Zum Hauptinhalt springen

ValkarnTaskCompletionSource

ValkarnTaskCompletionSource<T> und das nicht-generische ValkarnTaskCompletionSource geben Ihnen manuelle Kontrolle über ein ValkarnTask. Sie sind das asynchrone Äquivalent dazu, das Ergebnis selbst zu schreiben — Sie halten ein Source-Objekt, reichen dessen .Task an Aufrufer weiter und lösen es dann auf, verursachen einen Fehler oder brechen es von einer separaten Aufrufstelle aus ab.

Dies ist das Valkarn Tasks-Äquivalent von TaskCompletionSource<T> aus der BCL, aber gestützt durch ValkarnTask.Promise<T>, um das Allokationsmodell konsistent mit dem Rest der Bibliothek zu halten.


ValkarnTaskCompletionSource<T>

public class ValkarnTaskCompletionSource<T>
{
public ValkarnTask<T> Task { get; }
public bool TrySetResult(T result);
public bool TrySetException(Exception ex);
public bool TrySetCanceled();
}

Task

public ValkarnTask<T> Task { get; }

Die Task, die awaiting-Code beobachten wird. Verteilen Sie diese an alle Aufrufer, die auf das Ergebnis warten müssen. Mehrere Aufrufer können dieselbe Task gleichzeitig awaiten.

TrySetResult

public bool TrySetResult(T result);

Schließt die Task erfolgreich mit dem angegebenen Wert ab. Alle wartenden Fortsetzungen werden fortgesetzt. Gibt true zurück, wenn der Abschluss akzeptiert wurde; gibt false zurück, wenn die Task bereits abgeschlossen war (durch einen vorherigen TrySet*-Aufruf). Wirft nie.

TrySetException

public bool TrySetException(Exception ex);

Verursacht einen Fehler in der Task mit der angegebenen Ausnahme. Awaiting-Code erhält die Ausnahme, wenn er await aufruft. Wirft ArgumentNullException, wenn ex null ist. Gibt true zurück, wenn akzeptiert, false wenn bereits abgeschlossen.

TrySetCanceled

public bool TrySetCanceled();

Bricht die Task ab. Awaiting-Code erhält eine OperationCanceledException. Gibt true zurück, wenn akzeptiert, false wenn bereits abgeschlossen.


Nicht-generisches ValkarnTaskCompletionSource

Es gibt keine separate nicht-generische ValkarnTaskCompletionSource-Klasse in der öffentlichen API. Für manuell gesteuerte void-Tasks verwenden Sie ValkarnTask.Promise direkt:

var promise = new ValkarnTask.Promise();
ValkarnTask task = promise.Task;

promise.TrySetResult(); // abschließen
promise.TrySetException(ex);
promise.TrySetCanceled();

ValkarnTask.Promise bietet dieselbe TrySet*-Oberfläche und denselben Doppelabschluss-Schutz, erzeugt aber ein nicht-generisches ValkarnTask statt eines ValkarnTask<T>.


Doppelabschluss-Schutz

Alle TrySet*-Methoden können jederzeit von einem beliebigen Thread sicher aufgerufen werden, einschließlich gleichzeitig. Der erste Aufruf, der ein Compare-and-Swap auf der internen Zustandsmaschine gewinnt, gelingt; jeder nachfolgende Aufruf gibt false zurück und hat keine Wirkung. Das bedeutet:

  • TrySetResult zweimal aufzurufen hat beim zweiten Aufruf keine Wirkung.
  • TrySetResult nach TrySetException aufzurufen hat keine Wirkung.
  • Zwei Threads, die gleichzeitig um den Abschluss derselben Source wetteifern, sind sicher — einer gewinnt, einer wird lautlos ignoriert.

Wenn Sie wissen möchten, welcher Aufrufer „gewonnen" hat, prüfen Sie den Rückgabewert. Wenn Sie sich nicht darum kümmern (Fire-and-Forget-Signal), können Sie ihn sicher ignorieren.

// Sicheres Rennen — nur eines davon wird die Task tatsächlich abschließen
_ = source.TrySetResult(value);
_ = source.TrySetCanceled();

Häufige Muster

Eine callback-basierte API überbrücken

Viele Unity- und Plattform-APIs liefern Ergebnisse über Callbacks statt async/await. ValkarnTaskCompletionSource<T> lässt Sie diese sauber umhüllen.

public ValkarnTask<Texture2D> LoadTextureAsync(string url)
{
var tcs = new ValkarnTaskCompletionSource<Texture2D>();

StartCoroutine(LoadCoroutine(url, tcs));

return tcs.Task;
}

IEnumerator LoadCoroutine(string url, ValkarnTaskCompletionSource<Texture2D> tcs)
{
var request = UnityWebRequestTexture.GetTexture(url);
yield return request.SendWebRequest();

if (request.result == UnityWebRequest.Result.Success)
tcs.TrySetResult(DownloadHandlerTexture.GetContent(request));
else
tcs.TrySetException(new Exception(request.error));
}

Aufrufer können nun einfach die zurückgegebene Task awaiten:

Texture2D tex = await LoadTextureAsync("https://example.com/image.png");

Einmaliges Signal (asynchrones Gate)

Verwenden Sie ValkarnTask.Promise, wenn Sie ein Signal benötigen, das einmal ausgelöst wird und eine beliebige Anzahl von Wartenden entsperrt. Dies ähnelt einem ManualResetEventSlim, ist aber nativ asynchron.

public class AsyncGate
{
readonly ValkarnTask.Promise _promise = new();

// Eine beliebige Anzahl von Aufrufern kann dies abwarten
public ValkarnTask WaitAsync() => _promise.Task;

// Einmal aufrufen, um alle zu entsperren
public void Open() => _promise.TrySetResult();
}

// Verwendung
var gate = new AsyncGate();

// Mehrere Systeme warten unabhängig auf das Gate
async ValkarnTask SystemAAsync()
{
await gate.WaitAsync();
// nach dem Öffnen des Gates fortfahren
}

async ValkarnTask SystemBAsync()
{
await gate.WaitAsync();
// zur selben Zeit wie SystemA fortfahren
}

// Irgendwo anders — Gate öffnen
gate.Open();

Sobald TrySetResult() aufgerufen wird, schließen alle aktuellen und zukünftigen await-Aufrufe auf der Task sofort ab (synchron, wenn die Task bereits abgeschlossen ist, wenn sie laufen).

Eine Drittanbieter-asynchrone Operation mit Abbruchunterstützung umhüllen

public ValkarnTask<Result> RunWithTimeoutAsync(
Func<ValkarnTask<Result>> operation,
float timeoutSeconds,
CancellationToken ct)
{
var tcs = new ValkarnTaskCompletionSource<Result>();

RunCoreAsync(tcs, operation, timeoutSeconds, ct).Forget();

return tcs.Task;
}

async ValkarnTask RunCoreAsync(
ValkarnTaskCompletionSource<Result> tcs,
Func<ValkarnTask<Result>> operation,
float timeout,
CancellationToken ct)
{
using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
linked.CancelAfter(TimeSpan.FromSeconds(timeout));

try
{
var result = await operation();
tcs.TrySetResult(result);
}
catch (OperationCanceledException)
{
tcs.TrySetCanceled();
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}

Verzögertes Initialisierungsgate

Ein häufiges Unity-Muster ist es, eine „bereit"-Task verfügbar zu machen, auf die Komponenten warten können, unabhängig davon, ob die Initialisierung bereits begonnen hat.

public class ServiceBus : MonoBehaviour
{
readonly ValkarnTask.Promise _readyPromise = new();

// Jeder kann dies zu jedem Zeitpunkt abwarten — vor oder nach Initialize()
public ValkarnTask Ready => _readyPromise.Task;

async void Start()
{
await LoadConfigAsync();
await ConnectAsync();
_readyPromise.TrySetResult(); // entsperrt alle Wartenden
}
}

// In einer anderen Komponente
async ValkarnTask OnEnableAsync()
{
await ServiceBus.Instance.Ready; // wartet, wenn noch nicht bereit, gibt sofort zurück, wenn bereits bereit
DoWork();
}

Beziehung zu ValkarnTask.Promise

ValkarnTaskCompletionSource<T> ist ein dünner öffentlicher Wrapper um ValkarnTask.Promise<T>. Beide bieten dieselbe Funktionalität. Der Unterschied liegt in der Namensgebungskonvention:

TypGibt zurückHäufige Verwendung
ValkarnTaskCompletionSource<T>ValkarnTask<T>Öffentliche API, spiegelt BCL TaskCompletionSource<T>-Stil
ValkarnTask.Promise<T>ValkarnTask<T>Interne Verwendung, etwas direkter
ValkarnTask.PromiseValkarnTaskVoid-Signale (Gates, Events)

Alle drei stützen ihre Task auf einen ValkarnTaskCompletionCore<T>, die interne struct-basierte Zustandsmaschine, die Thread-Sicherheit und das Fortsetzungs-/Ergebnis-Rennen mithilfe eines CAS-basierten zweiphasigen Protokolls handhabt.