Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Timeout an async method implemented with TaskCompletionSource

I have a blackbox object that exposes a method to kick of an async operation, and an event fires when the operation is complete. I have wrapped that into an Task<OpResult> BlackBoxOperationAysnc() method using TaskCompletionSource - that works well.

However, in that async wrapper I'd like to manage completing the async call with a timeout error if the event is not received after a given timeout. Currently I manage it with a timer as:

public Task<OpResult> BlackBoxOperationAysnc() {     var tcs = new TaskCompletionSource<TestResult>();        const int timeoutMs = 20000;     Timer timer = new Timer(_ => tcs.TrySetResult(OpResult.Timeout),                             null, timeoutMs, Timeout.Infinite);      EventHandler<EndOpEventArgs> eventHandler = (sender, args) => {         ...         tcs.TrySetResult(OpResult.BlarBlar);     }     blackBox.EndAsyncOpEvent += eventHandler;     blackBox.StartAsyncOp();     return tcs.Task; } 

Is that the only way to manage a timeout? Is there someway without setting up my own timer - I couldn't see anything timeout built into TaskCompletionSource?

like image 550
Ricibob Avatar asked Sep 12 '13 09:09

Ricibob


People also ask

What is taskcompletionsource in async methods?

Aside from void, which has specific use cases, Task is the primary return type used with async methods, along with the lesser used ValueTask. It might be surprising, though, to learn that the similarly named TaskCompletionSource is not an acceptable return type for async methods.

What is the use of the task class in async/await?

When working with async / await in C# you end up using the Task class quite a bit. Aside from void, which has specific use cases, Task is the primary return type used with async methods, along with the lesser used ValueTask.

When to use taskcompletionsource<T> class?

… when used with async/await. TaskCompletionSource<T>class is a very useful facility if you want to control the lifetime of a task manually. Here is a canonical example when TaskCompletionSourceis used for converting the event-based asynchronous code to the Task-based pattern:

How does the main thread wait for taskcompletionsource?

As you can see, the main thread waited until tcs.SetResult (true) was called; this triggered completion of the TaskCompletionSource’s underlying task (which the main thread was awaiting), and allowed the main thread to resume. Aside from SetResult (), TaskCompletionSource offers methods to cancel a task or fault it with an exception.


1 Answers

You could use CancellationTokenSource with timeout. Use it together with your TaskCompletionSource like this.

E.g.:

public Task<OpResult> BlackBoxOperationAysnc() {     var tcs = new TaskCompletionSource<TestResult>();      const int timeoutMs = 20000;     var ct = new CancellationTokenSource(timeoutMs);     ct.Token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false);      EventHandler<EndOpEventArgs> eventHandler = (sender, args) => {         ...         tcs.TrySetResult(OpResult.BlarBlar);     }     blackBox.EndAsyncOpEvent += eventHandler;     blackBox.StartAsyncOp();     return tcs.Task; } 

Updated, here's a complete functional example:

using System; using System.ComponentModel; using System.Threading; using System.Threading.Tasks;  namespace ConsoleApplication {     public class Program     {         // .NET 4.5/C# 5.0: convert EAP pattern into TAP pattern with timeout         public async Task<AsyncCompletedEventArgs> BlackBoxOperationAsync(             object state,             CancellationToken token,             int timeout = Timeout.Infinite)         {             var tcs = new TaskCompletionSource<AsyncCompletedEventArgs>();             using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token))             {                 // prepare the timeout                 if (timeout != Timeout.Infinite)                 {                     cts.CancelAfter(timeout);                 }                  // handle completion                 AsyncCompletedEventHandler handler = (sender, args) =>                 {                     if (args.Cancelled)                         tcs.TrySetCanceled();                     else if (args.Error != null)                         tcs.SetException(args.Error);                     else                         tcs.SetResult(args);                 };                  this.BlackBoxOperationCompleted += handler;                 try                 {                     using (cts.Token.Register(() => tcs.SetCanceled(), useSynchronizationContext: false))                     {                         this.StartBlackBoxOperation(null);                         return await tcs.Task.ConfigureAwait(continueOnCapturedContext: false);                     }                 }                 finally                 {                     this.BlackBoxOperationCompleted -= handler;                 }             }         }          // emulate async operation         AsyncCompletedEventHandler BlackBoxOperationCompleted = delegate { };          void StartBlackBoxOperation(object state)         {             ThreadPool.QueueUserWorkItem(s =>             {                 Thread.Sleep(1000);                 this.BlackBoxOperationCompleted(this, new AsyncCompletedEventArgs(error: null, cancelled: false, userState: state));             }, state);         }          // test         static void Main()         {             try             {                 new Program().BlackBoxOperationAsync(null, CancellationToken.None, 1200).Wait();                 Console.WriteLine("Completed.");                 new Program().BlackBoxOperationAsync(null, CancellationToken.None, 900).Wait();             }             catch (Exception ex)             {                 while (ex is AggregateException)                     ex = ex.InnerException;                 Console.WriteLine(ex.Message);             }             Console.ReadLine();         }     } } 

A .NET 4.0/C# 4.0 vesion can be found here, it takes advantage of the compiler-generated IEnumerator state machine.

like image 53
noseratio Avatar answered Sep 19 '22 20:09

noseratio