Nicht generische TaskCompletionSource oder Alternative

73

Ich arbeite mit einem Warnfenster (Telerik WPF), das normalerweise asynchron angezeigt wird (Code wird weiterhin ausgeführt, solange es geöffnet ist), und ich möchte es mithilfe von async / await synchronisieren.

Ich arbeite damit, TaskCompletionSourceaber diese Klasse ist generisch und gibt ein Objekt zurück, wie Task<bool>wenn ich nur eine Ebene Taskohne Rückgabewert möchte .

public Task<bool> ShowAlert(object message, string windowTitle)
{
    var dialogParameters = new DialogParameters { Content = message };

    var tcs = new TaskCompletionSource<bool>();
    dialogParameters.Closed += (s, e) => tcs.TrySetResult(true);

    RadWindow.Alert(dialogParameters);

    return tcs.Task;
}

Der Code, der diese Methode aufruft, lautet

await MessageBoxService.ShowAlert("The alert text.")

Wie kann ich eine nicht generische Aufgabe zurückgeben, die ähnlich funktioniert und auf die ich warten kann, bis das dialogParameters.ClosedEreignis ausgelöst wird? Ich verstehe, dass ich das bool, was in diesem Code zurückgegeben wird, einfach ignorieren könnte . Ich suche nach einer anderen Lösung.

Kevin Kalitowski
quelle

Antworten:

97

Die Methode kann geändert werden in:

public Task ShowAlert(object message, string windowTitle)

Task<bool>erbt von, Taskso dass Sie zurückkehren können, Task<bool>während Sie nur Taskdem Anrufer ausgesetzt sind

Bearbeiten:

Ich habe ein Microsoft-Dokument mit dem Titel "Das aufgabenbasierte asynchrone Muster" ( http://www.microsoft.com/en-us/download/details.aspx?id=19957 ) von Stephen Toub mit dem folgenden empfohlenen Auszug gefunden das gleiche Muster.

Es gibt kein nicht generisches Gegenstück zu TaskCompletionSource <TResult>. Task <TResult> leitet sich jedoch von Task ab, und daher kann die generische TaskCompletionSource <TResult> für E / A-gebundene Methoden verwendet werden, die einfach eine Task zurückgeben, indem sie eine Quelle mit einem Dummy-TResult verwenden (Boolean ist eine gute Standardauswahl, und Wenn ein Entwickler besorgt ist, dass ein Verbraucher der Task sie auf eine Task <TResult> herunterwirft, kann ein privater TResult-Typ verwendet werden.

Kevin Kalitowski
quelle
29
Wenn nur voidals Typparameter akzeptiert würde, müssten Sie diese Verzerrungen nicht ausführen.
MgSam
4
Der Vorteil von objectover bool(wie in Stephens Antwort) ist, dass dies boolbedeuten könnte, dass es eine Bedeutung gibt.
Matthijs Wessels
7
Betrachten Sie TaskCompletionSource <System.Reactive.Unit>, um dies dem Leser klarer zu machen.
RichB
5
@ RichB Interessant. Ich habe diese Klasse noch nie gesehen, was mich wundert. Ist es wirklich klarer, wenn ich und der nächste nicht wissen, was es bedeutet? <wink> Guter Vorschlag.
Kevin Kalitowski
3
Scala: scala-lang.org/api/current/index.html#scala.Unit F #: msdn.microsoft.com/en-us/library/dd483472.aspx Wikipedia: en.wikipedia.org/wiki/Unit_type Ich denke, das ist es klarer als ein überflüssiger Bool oder Objekt, selbst wenn der Leser die oben genannten Dokumente liest.
RichB
52

Wenn Sie keine Informationen verlieren möchten, besteht der übliche Ansatz darin, TaskCompletionSource<object>ein Ergebnis von zu verwenden und zu vervollständigen null. Dann geben Sie es einfach als Task.

Stephen Cleary
quelle
Leider können Sie SetCancel / SetException nicht ausführen, ohne den generischen Typparameter T zu kennen. Scheint ein großer Designfehler zu sein.
Grigory
@Grigory: Bist du sicher? Ab heute kann ich SetException problemlos verwenden.
Sebastian
1
@Sebastian: Das nicht generische Task.FromExceptionwurde in .NET 4.6 hinzugefügt.
Stephen Cleary
1
Einverstanden. Dies scheint ein großer Designfehler zu sein.
Christian Findlay
3

Nito.AsyncEx implementiert eine nicht generische TaskCompletionSourceKlasse, die Herrn @StephenCleary oben gutgeschrieben wurde.

georgiosd
quelle
Ich habe gerade dieses Repo heruntergeladen und von dort aus die TaskCompletionSource-Klasse abgerufen. Es macht Sinn, aber es ist immer noch das gleiche Problem. Obwohl eine Aufgabe zurückgegeben wird, können Sie Ihre Methoden nicht mit dem Schlüsselwort async markieren, sodass Sie nicht einmal das Schlüsselwort await verwenden können.
Christian Findlay
1
@MelbourneDeveloper Sie sprechen von etwas anderem als dem Originalplakat. TaskCompletionSourcewird verwendet, um eine asynchrone Schnittstelle zum Synchronisieren von Code mit Ereignissen und dergleichen bereitzustellen. Sie können eine TaskCompletionSource.TaskGeldstrafe abwarten, sind sich also nicht sicher, wo das Problem liegt?
Georgiosd
Zu TaskCompletionSourceIhrer Information , die nicht generische Version von wurde in Version 5 der AsyncEx-Bibliothek entfernt: github.com/StephenCleary/AsyncEx/issues/176
mark.monteiro
1

Von @ Kevin Kalitowski

Ich habe ein Microsoft-Dokument mit dem Titel "Das aufgabenbasierte asynchrone Muster" von Stephen Toub gefunden: http://www.microsoft.com/en-us/download/details.aspx?id=19957.

In diesem Dokument gibt es ein Beispiel, das sich meiner Meinung nach mit dem Problem befasst, wie Kevin betont. Dies ist das Beispiel:

public static Task Delay(int millisecondsTimeout)
{
    var tcs = new TaskCompletionSource<bool>();
    new Timer(self =>
    {
        ((IDisposable)self).Dispose();
        tcs.TrySetResult(true);
    }).Change(millisecondsTimeout, -1);
    return tcs.Task;
}

Zuerst dachte ich, es sei nicht gut, weil man den Modifikator "async" ohne eine Kompilierungsnachricht nicht direkt zur Methode hinzufügen kann. Wenn Sie die Methode jedoch geringfügig ändern, wird die Methode mit async / await kompiliert:

public async static Task Delay(int millisecondsTimeout)
{
    var tcs = new TaskCompletionSource<bool>();
    new Timer(self =>
    {
        ((IDisposable)self).Dispose();
        tcs.TrySetResult(true);
    }).Change(millisecondsTimeout, -1);
    await tcs.Task;
}

Edit: Zuerst dachte ich, ich wäre über den Buckel gekommen. Wenn ich jedoch den entsprechenden Code in meiner App ausgeführt habe, lässt dieser Code die App nur hängen, wenn sie auf tcs.Task; wartet. Daher glaube ich immer noch, dass dies ein schwerwiegender Konstruktionsfehler in der asynchronen / wartenden c # -Syntax ist.

Christian Findlay
quelle
Ihr Codebeispiel funktioniert für mich. Ich kann den Code nicht wirklich in einem Kommentar posten, aber ich habe Folgendes getan und awaitGoAsync () bearbeitet:private static async Task GoAsync() { Console.WriteLine($"Starting await at: {DateTime.Now.ToLongTimeString()}"); await Delay(3000); Console.WriteLine($"Got past await at: {DateTime.Now.ToLongTimeString()}"); }
Kevin Kalitowski
Ich vermute, Ihre App hängt aufgrund eines Deadlocks: Sowohl der Timer als auch das Warten "laufen" auf demselben Thread, sodass der Timer nie die Möglichkeit erhält, das Ergebnis festzulegen, und das Warten kann niemals abgeschlossen werden. Versuchen Sie es await tcs.Task.ConfigureAwait(false)stattdessen.
Enzi
Überprüfen Sie die RunContinuationsAsynchronouslyOption.
Theodor Zoulias