async/await Task Timeout

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

转载于:https://www.cnblogs.com/HelloMyWorld/p/5526914.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/109001.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 微信小程序轮播图实现详解

    微信小程序轮播图实现详解微信小程序轮播图实现详解先上效果:wxml代码:<swiperautoplayinterval=”2000″circularindicator-color=”#0094ff”indicator-dotsindicator-active-color=”#ff0094″><!–swiper默认高度已经写死,无法改变、无法由内容撑开–><swiper-item><imagemode=”widthFix”src=”../../16151702

  • 以管理员身份运行IJ

    以管理员身份运行IJ

  • myeclipse中,项目上有个叉报错,文件没有错误[通俗易懂]

    myeclipse中,项目上有个叉报错,文件没有错误

  • 腾讯云服务器搭建NextCloud云盘[通俗易懂]

    腾讯云服务器搭建NextCloud云盘[通俗易懂]初衷我相信每个人都想要创造一点个人的小空间,存放某种东西(你懂的)。为了达到这个目的,顺便学习一下服务器的搭建,折腾了几天,终于算是搞定了,便记录如下。一准备思路:使用腾讯云服务器,部署LAMP。其实使用本地的电脑也是可以的,只是为了方便跨局域网访问,因为还没有学会如何绑定域名,如何用Nginx穿透局域网,所以只能借助别人的方便了。购买云服务器,略;给云服务器…

  • Spring Boot 系列:处理跨域请求

    一、何为跨域前端请求于后端处理符合三个要求(同一域名,同一端口,同一协议)下,即可访问,有一个不符合就会出现跨域问题。1.1一次正常的请求Controller层代码:@RequestMapping(&amp;amp;quot;/demo&amp;amp;quot;)@RestControllerpublicclassCorsTestController{@GetMapping(&amp;amp;quot;/sayHello&amp;amp;

  • Java线程同步:synchronized锁住的是代码还是对象

    Java线程同步:synchronized锁住的是代码还是对象在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。Synchronized既可以对代码块使用,也可以加在整个方法上。 关键是,不要认为给方法或者代码段加上synchronized就万事大吉,看下面一段代码:classSync{ publicsynchronizedvoidtest

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号