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上一會就弄懂了。記錄一下,算是這兩天的研究成果。

相關文章
相關標籤/搜索