Necesitamos tu ayuda para difundir nuestras reseñas acerca de las ciencias de la computación.
Solución:
Puedes usar TaskCompletetionSource
para crear un Task
que puede marcar como completado o cancelado. Aquí hay una posible implementación para un evento específico:
public Task WaitFirstMyEvent(Foo target, CancellationToken cancellationToken)
var tcs = new TaskCompletionSource
En C# 5 puedes usarlo así:
private async Task MyMethod()
...
await WaitFirstMyEvent(foo, cancellationToken);
...
Si desea esperar el evento de forma síncrona, también puede utilizar el Wait
método:
private void MyMethod()
...
WaitFirstMyEvent(foo, cancellationToken).Wait();
...
Aquí hay una versión más genérica, pero aún funciona solo para eventos con Action
firma:
public Task WaitFirstEvent(
Action subscribe,
Action unsubscribe,
CancellationToken cancellationToken)
var tcs = new TaskCompletionSource
Puedes usarlo así:
await WaitFirstEvent(
handler => foo.MyEvent += handler,
handler => foo.MyEvent -= handler,
cancellationToken);
Si desea que funcione con otras firmas de eventos (por ejemplo, EventHandler
), tendrá que crear sobrecargas separadas. No creo que haya una manera fácil de hacer que funcione para cualquier firma, especialmente porque la cantidad de parámetros no siempre es la misma.
Puede usar Rx para convertir el evento en un observable, luego en una tarea y finalmente esperar en esa tarea con su token/tiempo de espera.
Una ventaja que tiene sobre cualquiera de las soluciones existentes es que llama unsubscribe
en el hilo del evento, asegurando que su controlador no será llamado dos veces. (En su primera solución, soluciona esto de la siguiente manera: tcs.TrySetResult
en vez de tcs.SetResult
, pero siempre es bueno deshacerse de un “TryDoSomething” y simplemente asegurarse de que DoSomething siempre funcione).
Otra ventaja es la simplicidad del código. Es esencialmente una línea. Así que ni siquiera necesitas una función independiente. Puede alinearlo para que quede más claro qué hace exactamente su código, y puede hacer variaciones en el tema sin necesidad de una tonelada de parámetros opcionales (como su initializer
, o permitir esperar en N eventos, o renunciar a tiempos de espera/cancelaciones en instancias donde no son necesarios). Y tendrías los dos bool
valor de retorno y el actual result
en alcance cuando esté terminado, si eso es útil en absoluto.
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
...
public static bool WaitForSingleEvent(this CancellationToken token, Action onEvent, Action> subscribe, Action> unsubscribe, int msTimeout, Action initializer = null)
var task = Observable.FromEvent(subscribe, unsubscribe).FirstAsync().ToTask();
if (initializer != null)
initializer();
try
var finished = task.Wait(msTimeout, token);
if (finished) onEvent(task.Result);
return finished;
catch (OperationCanceledException) return false;