Asynchronously wait for Task to complete with timeout

后端 未结 16 1950
天命终不由人
天命终不由人 2020-11-21 17:49

I want to wait for a Task to complete with some special rules: If it hasn\'t completed after X milliseconds, I want to display a message to the user. And

16条回答
  •  有刺的猬
    2020-11-21 18:31

    Use a Timer to handle the message and automatic cancellation. When the Task completes, call Dispose on the timers so that they will never fire. Here is an example; change taskDelay to 500, 1500, or 2500 to see the different cases:

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            private static Task CreateTaskWithTimeout(
                int xDelay, int yDelay, int taskDelay)
            {
                var cts = new CancellationTokenSource();
                var token = cts.Token;
                var task = Task.Factory.StartNew(() =>
                {
                    // Do some work, but fail if cancellation was requested
                    token.WaitHandle.WaitOne(taskDelay);
                    token.ThrowIfCancellationRequested();
                    Console.WriteLine("Task complete");
                });
                var messageTimer = new Timer(state =>
                {
                    // Display message at first timeout
                    Console.WriteLine("X milliseconds elapsed");
                }, null, xDelay, -1);
                var cancelTimer = new Timer(state =>
                {
                    // Display message and cancel task at second timeout
                    Console.WriteLine("Y milliseconds elapsed");
                    cts.Cancel();
                }
                    , null, yDelay, -1);
                task.ContinueWith(t =>
                {
                    // Dispose the timers when the task completes
                    // This will prevent the message from being displayed
                    // if the task completes before the timeout
                    messageTimer.Dispose();
                    cancelTimer.Dispose();
                });
                return task;
            }
    
            static void Main(string[] args)
            {
                var task = CreateTaskWithTimeout(1000, 2000, 2500);
                // The task has been started and will display a message after
                // one timeout and then cancel itself after the second
                // You can add continuations to the task
                // or wait for the result as needed
                try
                {
                    task.Wait();
                    Console.WriteLine("Done waiting for task");
                }
                catch (AggregateException ex)
                {
                    Console.WriteLine("Error waiting for task:");
                    foreach (var e in ex.InnerExceptions)
                    {
                        Console.WriteLine(e);
                    }
                }
            }
        }
    }
    

    Also, the Async CTP provides a TaskEx.Delay method that will wrap the timers in tasks for you. This can give you more control to do things like set the TaskScheduler for the continuation when the Timer fires.

    private static Task CreateTaskWithTimeout(
        int xDelay, int yDelay, int taskDelay)
    {
        var cts = new CancellationTokenSource();
        var token = cts.Token;
        var task = Task.Factory.StartNew(() =>
        {
            // Do some work, but fail if cancellation was requested
            token.WaitHandle.WaitOne(taskDelay);
            token.ThrowIfCancellationRequested();
            Console.WriteLine("Task complete");
        });
    
        var timerCts = new CancellationTokenSource();
    
        var messageTask = TaskEx.Delay(xDelay, timerCts.Token);
        messageTask.ContinueWith(t =>
        {
            // Display message at first timeout
            Console.WriteLine("X milliseconds elapsed");
        }, TaskContinuationOptions.OnlyOnRanToCompletion);
    
        var cancelTask = TaskEx.Delay(yDelay, timerCts.Token);
        cancelTask.ContinueWith(t =>
        {
            // Display message and cancel task at second timeout
            Console.WriteLine("Y milliseconds elapsed");
            cts.Cancel();
        }, TaskContinuationOptions.OnlyOnRanToCompletion);
    
        task.ContinueWith(t =>
        {
            timerCts.Cancel();
        });
    
        return task;
    }
    

提交回复
热议问题