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

async/await Task Timeout的更多相关文章

  1. async,await,Task 的一些用法

    async,await,Task 的一些用法 private void Form1_Load(object sender, EventArgs e) { Display(); } public asy ...

  2. C#扫盲篇(四):.NET Core 的异步编程-只讲干货(async,await,Task)

    关于async,await,task的用法和解释这里就不要说明了,网上一查一大堆.至于为啥还要写这篇文章,主要是其他文章水分太多,不适合新手学习和理解.以下内容纯属个人理解,如果有误,请高手指正.本文 ...

  3. async await Task

    一.使用Task 引用命名空间 using System.Threading.Tasks; 1.工厂方式 Task.Factory.StartNew(() => {Console.WriteLi ...

  4. async await task.Result 卡死

    在如下代码中: public async Task<string> GetData() { return await DoWork(); } 在UI线程中调用 var data = Get ...

  5. async await Task 使用方法

    使用概述 C#的使用过程中,除了以前的Thread.ThreadPool等用来开一个线程用来处理异步的内容.还可以使用新特性来处理异步.比以前的Thread和AutoResetEvent.delege ...

  6. C#多线程和异步(二)——Task和async/await详解

    一.什么是异步 同步和异步主要用于修饰方法.当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法:当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务 ...

  7. C#多线程和异步(二)——Task和async/await详解(转载)

    一.什么是异步 同步和异步主要用于修饰方法.当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法:当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务 ...

  8. Task和async/await详解

    一.什么是异步 同步和异步主要用于修饰方法.当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法:当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务 ...

  9. C#多线程和异步——Task和async/await详解

    阅读目录 一.什么是异步 二.Task介绍 1 Task创建和运行 2 Task的阻塞方法(Wait/WaitAll/WaitAny) 3 Task的延续操作(WhenAny/WhenAll/Cont ...

随机推荐

  1. jqGrid使用setColProp方法动态改变列属性

    在使用jqGrid插件时,有时我们需要动态改变列的属性,可使用setColProp方法,用法如下 jQuery(”#grid_id”).setColProp('colname',{editoption ...

  2. JS字符串反序输出

    function IsReverse(text){ return text.split('').reverse().join(''); } IsReverse("你是我的小丫小苹果" ...

  3. js 循环li添加点击事件 (闭包的应用)

    var aLi = document.querySelectorAll('.article-tab li');  for (var i = 0; i <= aLi.length; i++) {  ...

  4. 用JS做一个简单的电商产品放大镜功能

    使用js制作一个简单的产品放大图 购物网站的产品页经常会放有一个产品展示图区.该图区有一个功能就是产品图的放大功能,移动左侧的焦点区域,可以放大细节部分观看,详情如下图.实现该功能的方法也非常简单. ...

  5. SharePoint 2013 图文开发系列之网站栏

    网站栏的本质,就是一个xml的描述文件,所以创建过程,基本就是通过Feature部署一个Xml文件,然后修改Xml文件的网站栏描述. 1.添加新项目,选择SharePoint 2013 空项目,如下图 ...

  6. sharepoint

    <script> $("#sideNavBox").css('display','none'); $("#contentBox").css('mar ...

  7. EventBus3.0源码解析

    本文主要介绍EventBus3.0的源码 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递. EventBus使用简单,并将事件发布和订阅充 ...

  8. SQL2008使用json.net实现XML与JSON互转

    借助CLR,首先实现字符串的互转,然后使用存储过程实现JSON2table     public class JsonFunction    {        /// <summary> ...

  9. PL/SQL重新编译包无反应

    前几天碰到一个有趣的事情:早上同事执行一个包很久没有反应,就中断了执行,发邮件让我帮忙查看具体情况,我用PL/SQL Developer登录后,找到这个包的过程中发现这个包的图标有红色叉叉,也就是说这 ...

  10. Linux如何查看JDK的安装路径

    如何在一台Linux服务器上查找JDK的安装路径呢? 有那些方法可以查找定位JDK的安装路径?是否有一些局限性呢? 下面总结了一下如何查找JDK安装路径的方法.   1:echo $JAVA_HOME ...