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

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • sched.h (版本4.16.7全部内容)

    sched.h (版本4.16.7全部内容)/*SPDX-License-Identifier:GPL-2.0*/#ifndef_LINUX_SCHED_H#define_LINUX_SCHED_H/**Define’structtask_struct’andprovidethemainscheduler*APIs(schedule(),wakeupvariants,etc.)*/…

    2022年10月22日
    0
  • Java应用结构规范[通俗易懂]

    Java应用结构规范[通俗易懂]简介:在Java程序开发中,命名和应用分层无疑是广大后端同胞的两大“痛点”,本文提供一种基于领域模型的轻量级应用分层结构设计,供大家参考。下面按分层结构、分层明细、调用关系、各层规范和通用代码工具展开介绍。作者|阿卓来源|阿里技术公众号序言在Java程序开发中,命名和应用分层无疑是广大后端同胞的两大“痛点”,本文提供一种基于领域模型的轻量级应用分层结构设计,供大家参考。下面按分层结构、分层明细、调用关系、各层规范和通用代码工具展开介绍。一分层结构web(前端请求层)

    2022年8月16日
    2
  • C#生成Excel出现8000401a的错误的另一种解决办法。「建议收藏」

    C#生成Excel出现8000401a的错误的另一种解决办法。「建议收藏」网上能搜到的解决办法,常见的就是以下3种,比如参考这个博客https://www.cnblogs.com/gavindou/archive/2012/08/29/2661757.html1,增加虚拟权限:在web.config里面增加的键值;要求administrator具有管理员权限,这种方案使用后确实可行,可是不利于部署,因为有经验的人都知道把一个最高权限的服务器帐号密码公开显示在配置…

    2022年8月22日
    5
  • 电阻参数_关于电阻的相关参数

    实际应用时,通常采用平均电阻温度系数,定义式:TCR(平均)=(R2-R1)/R1(T2-T1)有负温度系数、正温度系数及在某一特定温度下电阻只会发生突变的临界温度系数。紫铜的电阻温度系数为1/234.5℃。不同类型电阻温度稳定性从优到次,依次为:金属箔、线绕、金属膜、金属氧化膜、碳膜、有机实芯。1。镀金并不是为了减小电阻,而是因为金的化学性质非常稳定,不容易氧化,接头上镀金是为了防止接触不良(不…

    2022年4月8日
    45
  • kafka的使用场景举例_kafka一般用来做什么

    kafka的使用场景举例_kafka一般用来做什么关于消息队列的使用一、消息队列概述消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ二、消息队列应用场景以下介绍消息队列在实际应用中常用的使用场景。异步处理,应用解耦,流量削锋和消息通讯四个场景。2.1异步…

    2022年10月15日
    1
  • mysql修改root用户密码语法为_设置mysql的root密码

    mysql修改root用户密码语法为_设置mysql的root密码知道密码在清楚的知道密码的情况之下可以使用以下几种方式修改MySQL的密码。方式一登录mysql

    2022年8月13日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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