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)
上一篇 2021年9月15日 上午11:00
下一篇 2021年9月15日 下午12:00


相关推荐

  • IDEA配置方法注释模板

    IDEA配置方法注释模板打开 liveTemplate 第 4 步点击 选择 liveTemplate 第 6 步输入下方内容 param return Author huangyuewang Date date time 第 8 步选择文件类型 java 第 9 步按图输入 param 和 return 代码在下面 param 参数脚本 groovyScript if 1 length 2 return

    2026年3月16日
    3
  • Java学习之Spring框架入门篇

    Java学习之Spring框架入门篇0x00前言最近在学SSM的三大框架,上篇文章,已经更新了Mybatis的相关内容,那么这篇文章就来写写Spring的入门。0x01Spring概述S

    2021年12月12日
    44
  • Vim设置colorscheme小技巧

    Vim设置colorscheme小技巧VIM的颜色主题在/usr/share/vim/vim73/colors文件夹里。打开vim后在normal模式下输入“:colorscheme”查看当前的主题,修改主题使用命令“:colorschememycolor”,其中mycolor是你usr/share/vim/vim73/colors文件夹包含的文件名。也可以把这个命令吸入~/.vimrc配置文件中,这样每次打开vim都是你设定的主题

    2025年8月13日
    6
  • Nslookup命令_nslookup命令反解ip

    Nslookup命令_nslookup命令反解ip1、nslookup作用nslookup用于查询DNS的记录,查询域名解析是否正常,在网络故障时用来诊断网络问题2、查询a.直接查询nslookupdomain[dns-server]//如果没有指定dns服务器,就采用系统默认的dns服务器。b.查询其他记录nslookup-qt=typedomain[dns-server]type:…

    2022年10月19日
    7
  • 重庆职称计算机考试网,重庆职称计算机考试大纲

    重庆职称计算机考试网,重庆职称计算机考试大纲(2005年7月修订)根据重庆市职称改革办公室《关于调整全市专业技术人员职称外语和职称计算机考试有关规定的通知》(渝职改办〔2005〕99号),我市职称计算机考试分为6个级别,分别用字母A、B、C、D、E、F表示。我市职称计算机考试教材全书共有九个部分。其中计算机基础知识、中文Windows2000操作基础、中文Word2000、计算机网络基础与Internet应用、中文Access2000…

    2022年5月25日
    40
  • Kibana 使用 KQL 查询语法-kibana 常用查询语法

    Kibana 使用 KQL 查询语法-kibana 常用查询语法Kibana 查询语言 KQL 是一种使用自由文本搜索或基于字段的搜索过滤 Elasticsearc 数据的简单语法 KQL 仅用于过滤数据 并没有对数据进行排序或聚合的作用 KQL 能够在您键入时建议字段名称 值和运算符 建议的性能由 Kibana 设置控制 KQL 具有与 Lucene 查询语法不同的一组特性 KQL 能够查询嵌套字段和脚本字段 KQL 不支持正则表达式或使用模糊术语进行搜索 要使用旧版 Lucene 语法 请单击搜索字段旁边的 KQL 然后关闭 KQL 术语

    2026年3月18日
    1

发表回复

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

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