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 Task
s ä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:
TaskCompletionSource
Task
SetResult
// 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 Task
en 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 Task
en.
SetException(Exception exception)
Gör att någon som väntar på Task
en med await
måste fånga ett exception med try
/catch
.
SetCanceled()
Markerar Task
en 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 Task
en redan har avslutats kommer dessa anrop inte ge exception, men inte heller ändra resultatet. Anropet ignoreras i dessa fall alltså helt.