欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C# async/await任務超時處理的實現(xiàn)

 更新時間:2023年02月06日 10:06:44   作者:熊思宇  
本文主要介紹了C# async/await任務超時處理的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

一、需求

在之前的帖子中,介紹了 async / await 的用法,那么新的問題又來了,如果調(diào)用一個異步方法后,一直不給返回值結(jié)果怎么辦呢?這就涉及到怎么取消任務了。添加一個任務后,如果固定時間內(nèi)沒用返回結(jié)果,那么就取消執(zhí)行,并且在多個任務同時執(zhí)行的時候,依然按順序來執(zhí)行,這就是本文章要實現(xiàn)的功能。

二、Task取消任務

先介紹一下 Task 結(jié)束任務的傳統(tǒng)用法。在 C# 以前的2.0 等版本中,線程是可以強制終止的,到了后面,就不允許強制去結(jié)束線程的,后面微軟就提供了一個 CancellationTokenSource 相關(guān)的接口開源取消任務。于是我搜索了大量的帖子,一看全是各種抄襲,相互抄來抄去的,搞的我真的火冒三丈,完全是浪費時間,那么總結(jié)取消的方法如下:

namespace 取消任務3
{
    internal class Program
    {
        static CancellationTokenSource source = new CancellationTokenSource();
 
        static void Main(string[] args)
        {
            Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(100);
                    Console.WriteLine("oh my god");
                    source.Token.ThrowIfCancellationRequested();
                }
            }, source.Token);
 
            Thread.Sleep(2000);
            Console.WriteLine("取消任務");
            source.Cancel();
 
            Console.ReadKey();
        }
    }
}

運行:

這個可以取消任務是吧,那下面換一個寫法:

namespace 取消任務3
{
    internal class Program
    {
        static CancellationTokenSource source = new CancellationTokenSource();
 
        static void Main(string[] args)
        {
            Task.Run(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine("oh my god");
                source.Token.ThrowIfCancellationRequested();
            }, source.Token);
 
            Thread.Sleep(1000);
            Console.WriteLine("取消任務");
            source.Cancel();
 
            Console.ReadKey();
        }
    }
}

任務中等待三秒,我在等待一秒后取消任務,看看結(jié)果:

這回就不管用了,任務明明取消了,但結(jié)果依然執(zhí)行了。

根據(jù)他們寫的例子,可以總結(jié)一點,就是要想取消任務,在任務中,必須加入 for 或者 while 循環(huán),并且在下一輪循環(huán)中,執(zhí)行到 source.Token.ThrowIfCancellationRequested() 這句才能取消任務。

換個寫法,如果非得用 for 或者 while 循環(huán)這樣的語法才能取消任務,我在 while 循環(huán)中加入一個判斷,如果等于 true,直接跳出循環(huán),這不也是中斷了任務,所以說 CancellationTokenSource 真的意義不大

namespace 取消任務4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            bool isOut = false;
 
            var task1 = Task.Run(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    if (isOut) return;
 
                    Console.WriteLine("執(zhí)行中" + i);
                    Thread.Sleep(500);
                }
            });
 
            Thread.Sleep(2000);
            Console.WriteLine("取消任務");
            isOut= true;
 
            Console.ReadKey();
        }
    }
}

運行:

三、Task取消任務的回調(diào)

取消任務也是可以加入回調(diào)的,如下:

namespace 取消任務2
{
    internal class Program
    {
        static CancellationTokenSource source = new CancellationTokenSource();
 
        static void Main(string[] args)
        {
            var task1 = Task.Run(() =>
            {
                for (int i = 0; i < 100; i++)
                {
                    source.Token.ThrowIfCancellationRequested();
                    Console.WriteLine("執(zhí)行中" + i);
                    Thread.Sleep(500);
                }
            }, source.Token);
 
            //在指定的毫秒數(shù)后取消task執(zhí)行
            source.CancelAfter(2 * 1000);
 
            //取消任務后的回調(diào)
            source.Token.Register(() =>
            {
                //不延遲會獲取不到正確的狀態(tài)
                Thread.Sleep(50);
                Console.WriteLine("task1狀態(tài):" + task1.Status);
                Console.WriteLine("IsFaulted狀態(tài):" + task1.IsFaulted);//由于未處理的異常,任務已完成。
                Console.WriteLine("IsCompleted狀態(tài):" + task1.IsCompleted);//獲取一個值,該值指示任務是否已完成。
            });
 
            Console.ReadKey();
        }
    }
}

 運行:

四、Task超時處理的實現(xiàn)

在上面的介紹中可以看到,Task取消任務傳統(tǒng)的用法并不好用,必須在里面加上條件判斷,如果滿足條件就跳出 for 或者 while 循環(huán),達到方法執(zhí)行完成的目的,而并不是真的終止了任務。

那么這么需求要如何去完成呢,微軟官方也提供了一個叫 Task.WhenAny 接口,可以實現(xiàn)這個功能,下面就看看如何實現(xiàn)的。

新建一個基于 .Net6 的 Winform 項目,新建一個腳本 Lib.cs

namespace Utils
{
    public static class Lib
    {
 
        public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, Action<TResult>? successor = null)
        {
            //用于取消任務
            using CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource();
 
   
            //WhenAny 等待所有任務結(jié)束,這里加入了超時時間
            //ConfigureAwait 配置用來等待 任務1的警報,返回值可以獲取到改任務
            Task? completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)).ConfigureAwait(continueOnCapturedContext: false);
 
 
            //如果當前任務完成了,并且匹配
            if (completedTask == task)
            {
                //取消任務
                timeoutCancellationTokenSource.Cancel();
 
                //得到任務返回結(jié)果
                var result = await task.ConfigureAwait(continueOnCapturedContext: false);
 
                //執(zhí)行回調(diào)
                if(successor != null) 
                    successor(result);
 
                return true;
            }
            else //任務超時
                return false;
        }
    }
}

界面如下,就幾個按鈕

代碼:

using Utils;
 
namespace 異步編程
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
 
        private void button1_Click(object sender, EventArgs e)
        {
            Test1();
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            Test2();
        }
 
        private void Button_ClearConsole_Click(object sender, EventArgs e)
        {
            Console.Clear();
        }
 
        private async void Test1()
        {
            var task1 = Task.Run(() =>
            {
                Thread.Sleep(3000);
                return "task1";
            });
            bool res1 = await task1.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>
            {
                Console.WriteLine("-----------------------task1回調(diào):" + msg);
            });
 
            string isTimeout1 = res1 == true ? "沒超時" : "超時";
            Console.WriteLine("任務1:" + isTimeout1);
 
 
            var task2 = Task.Run(() =>
            {
                Thread.Sleep(3000);
                return "task2";
            });
 
            bool res2 = await task2.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>
            {
                Console.WriteLine("-----------------------task2回調(diào):" + msg);
            });
 
            string isTimeout2 = res2 == true ? "沒超時" : "超時";
            Console.WriteLine("任務2:" + isTimeout2);
        }
 
        private async void Test2()
        {
            var task3 = Task.Run(() =>
            {
                Thread.Sleep(3000);
                return "task3";
            });
 
            bool res3 = await task3.TryWithTimeoutAfter(TimeSpan.FromSeconds(4), (string msg) =>
            {
                Console.WriteLine("-----------------------task3回調(diào):" + msg);
            });
 
            string isTimeou3 = res3 == true ? "沒超時" : "超時";
            Console.WriteLine("任務2:" + isTimeou3);
        }
    }
}

按鈕1和按鈕2 方法里有三個異步方法,超時時間都是4秒,也就是說,如果方法在4秒之內(nèi)沒有返回值則為失敗。

分別點擊按鈕1,按鈕2

在按鈕1方法里有兩個異步方法,異步方法1執(zhí)行完成后,才能執(zhí)行異步方法2,所以異步方法2要比異步方法3更慢一些。

下面就將三個異步方法的超時時間改為1秒,看看效果:

返回超時,而且任務也沒有執(zhí)行,這樣就實現(xiàn)了我們的想要的效果了。 

五、Task.WhenAny 的異常

在一系列的測試中,我發(fā)現(xiàn)了 Task.WhenAny 這個接口在 .Net6 的控制臺項目的異常之處,執(zhí)行一次是正常的,如果在一個方法內(nèi)同時執(zhí)行多次,返回結(jié)果就開始亂了,在 Winform 項目中是沒有這種事的,下面開始演示。

新建一個基于 .Net6 的控制臺項目, 將上面的 Lib.cs 代碼復制到項目中來。

代碼:

using Utils;
 
namespace 異步編程2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            AwaitReturnValue();
 
            Console.ReadKey();
        }
 
        public static async void AwaitReturnValue()
        {
            var task1 = Task.Run(() =>
            {
                Thread.Sleep(3000);
                return "task1";
            });
 
 
            for (int i = 0; i < 10; i++)
            {
                bool res1 = await task1.TryWithTimeoutAfter(TimeSpan.FromSeconds(1), (string msg) =>
                {
                    Console.WriteLine("task1回調(diào):" + msg);
                });
 
                string isTimeout = res1 == true ? "沒超時" : "超時";
                Console.WriteLine(string.Format("結(jié)果{0}:{1}", i, isTimeout));
            }
        }
    }
}

運行:

任務前兩次是正確的,后面返回的基本全是錯誤的,原因我估計是 Task 任務內(nèi)部等待時間是3秒,調(diào)用了前兩次時間沒有超過三秒,所以返回是正確的,后面超過3秒后,全當在超時范圍內(nèi)返回了。

六、其他的寫法

超時取消任務的寫法可以有多種,其實萬變不離其宗,都是用 Task.WhenAny 方法實現(xiàn)的,代碼我全部放一個類里面了,有興趣的可以看看,有很多的高級語法,確實是值得學習的。

代碼:

namespace 異步編程1
{
    public static class Lib1
    {
        public static async Task<TResult?> TimeoutAfter<TResult>(this Task<TResult> task, int timeout)
        {
            using (var cancelToken = new CancellationTokenSource())
            {
                Task completedTask = await Task.WhenAny(task, Task.Delay(timeout, cancelToken.Token));
                if (completedTask == task)
                {
                    cancelToken.Cancel();
                    return await task;
                }
                else
                {
                    // 超時處理
                    Console.WriteLine("超時了");
                    return default;
                }
            }
        }
 
        public static async Task<bool> OnTimeout<T>(T t, Action<T> action, int waitms) where T : Task
        {
            if (!(await Task.WhenAny(t, Task.Delay(waitms)) == t))
            {
                action(t);
                return true;
            }
            else
            {
                return false;
            }
        }
 
        public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout, Action<TResult> successor)
        {
            using var timeoutCancellationTokenSource = new CancellationTokenSource();
            var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)).ConfigureAwait(continueOnCapturedContext: false);
 
            if (completedTask == task)
            {
                timeoutCancellationTokenSource.Cancel();
 
                // propagate exception rather than AggregateException, if calling task.Result.
                var result = await task.ConfigureAwait(continueOnCapturedContext: false);
                successor(result);
                return true;
            }
            else
                return false;
        }
 
        public static async Task<bool> BeforeTimeout(Task task, int millisecondsTimeout)
        {
            if (task.IsCompleted) return true;
            if (millisecondsTimeout == 0) return false;
            if (millisecondsTimeout == Timeout.Infinite)
            {
                await Task.WhenAll(task);
                return true;
            }
 
            var tcs = new TaskCompletionSource<object>();
 
            using (var timer = new Timer(state => ((TaskCompletionSource<object>)state).TrySetCanceled(), tcs, millisecondsTimeout, Timeout.Infinite))
            {
                return await Task.WhenAny(task, tcs.Task) == task;
            }
        }
    }
}

到此這篇關(guān)于C# async/await任務超時處理的實現(xiàn)的文章就介紹到這了,更多相關(guān)C# async/await任務超時內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論