async/await Task Timeout

async/await Task Timeout

在平常的电脑使用过程当中,估计最难以忍受的就是软件界面“卡住”“无响应”,在我有限的开发生涯中一直都是在挑战
它。在WPF中,主线程即UI线程,当咱们在UI线程中执行一个很耗时的操做,以致于UI线程没能继续绘制窗体,这时给人
的感受就是“卡住”了。程序员

很耗时的操做分为2种异步

  • 复杂计算async

  • I/O操做this

为了有一个良好的用户操做体验,咱们都会使用异步方法,在另一个线程中处理耗时的操做,当操做结束时,仅仅使用
UI线程更新结果到界面。.Net中的异步模型也有不少种,园子里有不少,不过用起来很舒服的仍是async/await。线程

async/await 的引入让咱们编写异步方法更加容易,它的目的就是使得咱们像同步方法同样编写异步方法。上面铺垫稍微
啰嗦了点。立刻进入正题,当咱们在await一个方法时,若是这个方法它是支持超时的,那么当超时时是以异常的形式来
通知咱们的,这样await如下的方法就没有办法执行了。code

注意:这里补充下,一个Task超时了,并不意味着这个Task就结束了,它仍是会运行,直到结束或是发生异常,一个超 时的Task返回的结果不该该被继续使用,应该丢弃token

提供了超时设置还好,可是若是这个方法没有超时设置,那岂不就是一直在这里傻等?那确定不,只有本身实现超时,一个
线程是没有办法作超时功能的。通常都是一个线程执行耗时操做,一个线程来计算超时,而且超时了要以异常的形式通知出来,因此
代码应该是这样的:事件

private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        try
        {
            await CanTimeoutTask(LongTimeWork, 6000);
            textBlock.Text = "XXD";
            await CanTimeoutTask(LongTimeWork, 3000);
        }
        catch (Exception ex)
        {
        }
    }

    private async Task CanTimeoutTask(Action action, int timeout)
    {
        var task1 = Task.Run(action);

        var task2 = Task.Delay(timeout);

        var firstTask = await Task.WhenAny(task1, task2);

        if (firstTask == task2)
        {
            throw new TimeoutException();
        }
    }

    private void LongTimeWork()
    {
        Thread.Sleep(5000);
    }

如此看来,已经知足咱们的需求了,可是做为一个上进的程序员,这么写真累啊,能不能提出一个简单易用的方法出来,
因而上Bing(这两天有点厌恶百度)搜索,看到这么一篇好像有点意思,本身琢磨着改进了一下,因此有了以下版本:开发

/// <summary>
/// 无返回值 可超时,可取消的Task
/// </summary>
public class TimeoutTask
{
    #region 字段
    private Action _action;
    private CancellationToken _token;
    private event AsyncCompletedEventHandler _asyncCompletedEvent;
    private TaskCompletionSource<AsyncCompletedEventArgs> _tcs;
    #endregion

    #region 静态方法
    public static async Task<AsyncCompletedEventArgs> StartNewTask(Action action, CancellationToken token)
    {
        return await TimeoutTask.StartNewTask(action, token, Timeout.Infinite);
    }

    public static async Task<AsyncCompletedEventArgs> StartNewTask(Action action, int timeout)
    {
        return await TimeoutTask.StartNewTask(action, CancellationToken.None, timeout);
    }

    public static async Task<AsyncCompletedEventArgs> StartNewTask(Action action, CancellationToken token,
        int timeout = Timeout.Infinite)
    {
        var task = new TimeoutTask(action, token, timeout);

        return await task.Run();
    }
    #endregion

    #region 构造

    protected TimeoutTask(Action action, int timeout) : this(action, CancellationToken.None, timeout)
    {

    }

    protected TimeoutTask(Action action, CancellationToken token) : this(action, token, Timeout.Infinite)
    {

    }

    protected TimeoutTask(Action action, CancellationToken token, int timeout = Timeout.Infinite)
    {
        _action = action;

        _tcs = new TaskCompletionSource<AsyncCompletedEventArgs>();

        if (timeout != Timeout.Infinite)
        {
            var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
            cts.CancelAfter(timeout);
            _token = cts.Token;
        }
        else
        {
            _token = token;
        }
    }
    #endregion

    #region 私有方法
    /// <summary>
    /// 运行
    /// </summary>
    /// <returns></returns>
    private async Task<AsyncCompletedEventArgs> Run()
    {
        _asyncCompletedEvent += AsyncCompletedEventHandler;

        try
        {
            using (_token.Register(() => _tcs.TrySetCanceled()))
            {
                ExecuteAction();
                return await _tcs.Task.ConfigureAwait(false);
            }

        }
        finally
        {
            _asyncCompletedEvent -= AsyncCompletedEventHandler;
        }

    }
    /// <summary>
    /// 执行Action
    /// </summary>
    private void ExecuteAction()
    {
        Task.Factory.StartNew(() =>
        {
            _action.Invoke();

            OnAsyncCompleteEvent(null);
        });
    }

    /// <summary>
    /// 异步完成事件处理
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void AsyncCompletedEventHandler(object sender, AsyncCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            _tcs.TrySetCanceled();
        }
        else if (e.Error != null)
        {
            _tcs.TrySetException(e.Error);
        }
        else
        {
            _tcs.TrySetResult(e);
        }
    }

    /// <summary>
    /// 触发异步完成事件
    /// </summary>
    /// <param name="userState"></param>
    private void OnAsyncCompleteEvent(object userState)
    {
        if (_asyncCompletedEvent != null)
        {
            _asyncCompletedEvent(this, new AsyncCompletedEventArgs(error: null, cancelled: false, userState: userState));
        }
    }
    #endregion
}

/// <summary>
/// 有返回值,可超时,可取消的Task
/// </summary>
/// <typeparam name="T"></typeparam>
public class TimeoutTask<T>
{
    #region 字段
    private Func<T> _func;
    private CancellationToken _token;
    private event AsyncCompletedEventHandler _asyncCompletedEvent;
    private TaskCompletionSource<AsyncCompletedEventArgs> _tcs;
    #endregion

    #region 静态方法
    public static async Task<T> StartNewTask(Func<T> func, CancellationToken token,
        int timeout = Timeout.Infinite)
    {
        var task = new TimeoutTask<T>(func, token, timeout);

        return await task.Run();
    }

    public static async Task<T> StartNewTask(Func<T> func, int timeout)
    {
        return await TimeoutTask<T>.StartNewTask(func, CancellationToken.None, timeout);
    }

    public static async Task<T> StartNewTask(Func<T> func, CancellationToken token)
    {
        return await TimeoutTask<T>.StartNewTask(func, token, Timeout.Infinite);
    }
    #endregion

    #region 构造
    protected TimeoutTask(Func<T> func, CancellationToken token) : this(func, token, Timeout.Infinite)
    {

    }

    protected TimeoutTask(Func<T> func, int timeout = Timeout.Infinite) : this(func, CancellationToken.None, timeout)
    {

    }

    protected TimeoutTask(Func<T> func, CancellationToken token, int timeout = Timeout.Infinite)
    {
        _func = func;

        _tcs = new TaskCompletionSource<AsyncCompletedEventArgs>();

        if (timeout != Timeout.Infinite)
        {
            var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
            cts.CancelAfter(timeout);
            _token = cts.Token;
        }
        else
        {
            _token = token;
        }
    }
    #endregion

    #region 私有方法
    /// <summary>
    /// 运行Task
    /// </summary>
    /// <returns></returns>
    private async Task<T> Run()
    {
        _asyncCompletedEvent += AsyncCompletedEventHandler;

        try
        {
            using (_token.Register(() => _tcs.TrySetCanceled()))
            {
                ExecuteFunc();
                var args = await _tcs.Task.ConfigureAwait(false);
                return (T)args.UserState;
            }

        }
        finally
        {
            _asyncCompletedEvent -= AsyncCompletedEventHandler;
        }

    }

    /// <summary>
    /// 执行
    /// </summary>
    private void ExecuteFunc()
    {
        ThreadPool.QueueUserWorkItem(s =>
        {
            var result = _func.Invoke();

            OnAsyncCompleteEvent(result);
        });
    }

    /// <summary>
    /// 异步完成事件处理
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void AsyncCompletedEventHandler(object sender, AsyncCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            _tcs.TrySetCanceled();
        }
        else if (e.Error != null)
        {
            _tcs.TrySetException(e.Error);
        }
        else
        {
            _tcs.TrySetResult(e);
        }
    }

    /// <summary>
    /// 触发异步完成事件
    /// </summary>
    /// <param name="userState"></param>
    private void OnAsyncCompleteEvent(object userState)
    {
        if (_asyncCompletedEvent != null)
        {
            _asyncCompletedEvent(this, new AsyncCompletedEventArgs(error: null, cancelled: false, userState: userState));
        }
    }
    #endregion
}

使用起来也很方便get

private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        try
        {
            await TimeoutTask.StartNewTask(LongTimeWork, 6000);

            var result = await TimeoutTask<string>.StartNewTask(LongTimeWork2, 2000);

            textBlock.Text = result;
        }
        catch (Exception ex)
        {
        }
    }

    private void LongTimeWork()
    {
        Thread.Sleep(5000);
    }

    private string LongTimeWork2()
    {
        Thread.Sleep(5000);
        return "XXD";
    }

其中有一些不多见的CancellationTokenSource CancellationToken TaskCompletionSource AsyncCompletedEventHandler AsyncCompletedEventArgs 不要怕,MSDN上一会就弄懂了。记录一下,算是这两天的研究成果。

相关文章
相关标签/搜索