在平常的電腦使用過程當中,估計最難以忍受的就是軟件界面「卡住」「無響應」,在我有限的開發生涯中一直都是在挑戰
它。在WPF中,主線程即UI線程,當咱們在UI線程中執行一個很耗時的操做,以致於UI線程沒能繼續繪製窗體,這時給人
的感受就是「卡住」了。程序員
很耗時
的操做分爲2種異步
複雜計算async
I/O操做this
爲了有一個良好的用戶操做體驗,咱們都會使用異步方法,在另一個線程中處理耗時的操做,當操做結束時,僅僅使用
UI線程更新結果到界面。.Net中的異步模型也有不少種,園子裏有不少,不過用起來很舒服的仍是async/await。線程
async/await 的引入讓咱們編寫異步方法更加容易,它的目的就是使得咱們像同步方法同樣編寫異步方法。上面鋪墊稍微
囉嗦了點。立刻進入正題,當咱們在await一個方法時,若是這個方法它是支持超時的,那麼當超時時是以異常
的形式來
通知咱們的,這樣await如下的方法就沒有辦法執行了。code
注意:這裏補充下,一個Task超時了,並不意味着這個Task就結束了,它仍是會運行,直到結束或是發生異常,一個超 時的Task返回的結果不該該被繼續使用,應該丟棄
token
提供了超時設置還好,可是若是這個方法沒有超時設置,那豈不就是一直在這裏傻等?那確定不,只有本身實現超時,一個
線程是沒有辦法作超時功能的。通常都是一個線程執行耗時操做,一個線程來計算超時,而且超時了要以異常的形式通知出來,因此
代碼應該是這樣的:事件
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 }
使用起來也很方便get
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上一會就弄懂了。記錄一下,算是這兩天的研究成果。