WORDS
TaskCompletionSource
I C# finns flera olika mönster för att hantera asynkronicitet. Det generella sättet att konvertera vilket mönster som helst till en Tasks är att använda en TaskCompletionSource. Därigenom gör man det möjligt att konsumera det andra mönstret via async/await.
Ett minimalt exempel kräver tre delar:
TaskCompletionSourceTaskSetResult// Skapa en källa som styr hur en Task kan avslutas. var completion = new TaskCompletionSource<int>(); // Hämta den Task som kan avslutas. Task<int> task = completion.Task; // Sätt resultatet till 4. På så vis avslutas den Task vi hämtade i // föregående steg. completion.SetResult(4);
Från event till Task
Event kan användas för att modellera en ström av händelser i C#. En Task kan bara representera ett framtida värde, så det går inte att ersätta alla event med en Task. Om man däremot bara är intresserad av nästa gång då eventet inträffar kan man använda en TaskCompletionSource för att konvertera detta till en Task.
// Ett event. Behöver inte vara deklarerat i samma klass.
public event EventHandler<int> IntEvent;
/// <summary>
/// Returnera en Task som avslutas nästa gång IntEvent inträffar.
/// </summary>
public Task<int> NextInt()
{
// Skapa Task-källan.
var completion = new TaskCompletionSource<int>();
// Lyssna på eventet IntEvent. När eventet inträffar, kommer
// `handler` att anropas.
IntEvent += handler;
// Returnera den Task som kommer att avslutas senare.
return completion.Task;
// Deklarera en lokal funktion inuti metoden.
void handler(object sender, int result)
{
// Sluta lyssna på eventet IntEvent. Vi är ändå bara
// intresserade av första gången eventet inträffar.
IntEvent -= handler;
// Avsluta den Task som returnerats, genom att sätta resultatet
// via TaskCompletionSource.
completion.SetResult(result);
}
}
Om eventet IntEvent aldrig inträffar, så kommer heller aldrig den returnerade Tasken att avslutas. I så fall kommer ett anrop till await NextInt() eller NextInt().Result aldrig att returnera.
Övriga metoder
Förutom SetResult beskrivet ovanför, stödjer TaskCompletionSource flera andra metoder för att avsluta den resulterande Tasken.
SetException(Exception exception)
Gör att någon som väntar på Tasken med await måste fånga ett exception med try/catch.
SetCanceled()
Markerar Tasken som avbruten. Också detta kräver att någon som väntar med await måste fånga ett exception.
TrySetResult(), TrySetException(), TrySetCanceled()
Om man redan har avslutat sin Task genom att anropa SetResult, SetException eller SetCanceled, så ger nya anrop till dessa metoder ett exception. Istället kan man anropa motsvarande variant med prefixet Try. Om Tasken redan har avslutats kommer dessa anrop inte ge exception, men inte heller ändra resultatet. Anropet ignoreras i dessa fall alltså helt.