• async/await Task Timeout


    async/await Task Timeout

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

    很耗时的操作分为2种

    • 复杂计算

    • I/O操作

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

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

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

    提供了超时设置还好,但是如果这个方法没有超时设置,那岂不就是一直在这里傻等?那肯定不,只有自己实现超时,一个
    线程是没有办法做超时功能的。一般都是一个线程执行耗时操作,一个线程来计算超时,并且超时了要以异常的形式通知出来,所以
    代码应该是这样的:

        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
    }
    

    使用起来也很方便

        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上一会就弄懂了。记录一下,算是这两天的研究成果。

  • 相关阅读:
    sql笔记
    虚函数和抽象类笔记
    构造函数和静态构造函数 笔记
    在与sql server 建立连接时出现与网络相关的或特定于实例的错误
    类型转换 笔记
    test
    document.body和document.documentElement比较
    sql2005 存储过程实现分页
    新闻发布用到的存储过程和触发器
    SQL数据查询实例1
  • 原文地址:https://www.cnblogs.com/HelloMyWorld/p/5526914.html
Copyright © 2020-2023  润新知