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

C#多線程系列之任務(wù)基礎(chǔ)(三)

 更新時間:2022年02月14日 10:28:58   作者:癡者工良  
本文詳細講解了C#多線程的任務(wù)基礎(chǔ),文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

TaskAwaiter

先說一下 TaskAwaiterTaskAwaiter 表示等待異步任務(wù)完成的對象并為結(jié)果提供參數(shù)。

Task 有個 GetAwaiter() 方法,會返回TaskAwaiter 或TaskAwaiter<TResult>,TaskAwaiter 類型在 System.Runtime.CompilerServices 命名空間中定義。

TaskAwaiter 類型的屬性和方法如下:

屬性:

屬性說明
IsCompleted獲取一個值,該值指示異步任務(wù)是否已完成。

方法:

方法說明
GetResult()結(jié)束異步任務(wù)完成的等待。
OnCompleted(Action)將操作設(shè)置為當 TaskAwaiter 對象停止等待異步任務(wù)完成時執(zhí)行。
UnsafeOnCompleted(Action)計劃與此 awaiter 相關(guān)異步任務(wù)的延續(xù)操作。

使用示例如下:

        static void Main()
        {
            Task<int> task = new Task<int>(()=>
            {
                Console.WriteLine("我是前驅(qū)任務(wù)");
                Thread.Sleep(TimeSpan.FromSeconds(1));
                return 666;
            });

            TaskAwaiter<int> awaiter = task.GetAwaiter();

            awaiter.OnCompleted(()=>
            {
                Console.WriteLine("前驅(qū)任務(wù)完成時,我就會繼續(xù)執(zhí)行");
            });
            task.Start();

            Console.ReadKey();
        }

另外,我們前面提到過,任務(wù)發(fā)生未經(jīng)處理的異常,任務(wù)被終止,也算完成任務(wù)。

延續(xù)的另一種方法

上一節(jié)我們介紹了 .ContinueWith() 方法來實現(xiàn)延續(xù),這里我們介紹另一個延續(xù)方法 .ConfigureAwait()。

.ConfigureAwait() 如果要嘗試將延續(xù)任務(wù)封送回原始上下文,則為 true;否則為 false。

我來解釋一下, .ContinueWith() 延續(xù)的任務(wù),當前驅(qū)任務(wù)完成后,延續(xù)任務(wù)會繼續(xù)在此線程上繼續(xù)執(zhí)行。這種方式是同步的,前者和后者連續(xù)在一個線程上運行。

.ConfigureAwait(false) 方法可以實現(xiàn)異步,前驅(qū)方法完成后,可以不理會后續(xù)任務(wù),而且后續(xù)任務(wù)可以在任意一個線程上運行。這個特性在 UI 界面程序上特別有用。

可以參考:https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f

其使用方法如下:

        static void Main()
        {
            Task<int> task = new Task<int>(()=>
            {
                Console.WriteLine("我是前驅(qū)任務(wù)");
                Thread.Sleep(TimeSpan.FromSeconds(1));
                return 666;
            });

            ConfiguredTaskAwaitable<int>.ConfiguredTaskAwaiter awaiter = task.ConfigureAwait(false).GetAwaiter();

            awaiter.OnCompleted(()=>
            {
                Console.WriteLine("前驅(qū)任務(wù)完成時,我就會繼續(xù)執(zhí)行");
            });
            task.Start();

            Console.ReadKey();
        }

ConfiguredTaskAwaitable<int>.ConfiguredTaskAwaiter 擁有跟 TaskAwaiter 一樣的屬性和方法。

.ContinueWith() 跟 .ConfigureAwait(false) 還有一個區(qū)別就是 前者可以延續(xù)多個任務(wù)和延續(xù)任務(wù)的任務(wù)(多層)。后者只能延續(xù)一層任務(wù)(一層可以有多個任務(wù))。

另一種創(chuàng)建任務(wù)的方法

前面提到提到過,創(chuàng)建任務(wù)的三種方法:new Task()、Task.Run()、Task.Factory.SatrtNew(),現(xiàn)在來學習第四種方法:TaskCompletionSource<TResult> 類型。

我們來看看 TaskCompletionSource<TResulr> 類型的屬性和方法:

屬性:

屬性說明
Task獲取由此 Task 創(chuàng)建的 TaskCompletionSource。

方法:

方法說明
SetCanceled()將基礎(chǔ) Task 轉(zhuǎn)換為 Canceled 狀態(tài)。
SetException(Exception)將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并將其綁定到一個指定異常上。
SetException(IEnumerable)將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并對其綁定一些異常對象。
SetResult(TResult)將基礎(chǔ) Task 轉(zhuǎn)換為 RanToCompletion 狀態(tài)。
TrySetCanceled()嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Canceled 狀態(tài)。
TrySetCanceled(CancellationToken)嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Canceled 狀態(tài)并啟用要存儲在取消的任務(wù)中的取消標記。
TrySetException(Exception)嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并將其綁定到一個指定異常上。
TrySetException(IEnumerable)嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 Faulted 狀態(tài),并對其綁定一些異常對象。
TrySetResult(TResult)嘗試將基礎(chǔ) Task 轉(zhuǎn)換為 RanToCompletion 狀態(tài)。

TaskCompletionSource<TResulr> 類可以對任務(wù)的生命周期做控制。

首先要通過 .Task 屬性,獲得一個 Task 或 Task<TResult> 。

            TaskCompletionSource<int> task = new TaskCompletionSource<int>();
            Task<int> myTask = task.Task;	//  Task myTask = task.Task;

然后通過 task.xxx() 方法來控制 myTask 的生命周期,但是呢,myTask 本身是沒有任務(wù)內(nèi)容的。

使用示例如下:

        static void Main()
        {
            TaskCompletionSource<int> task = new TaskCompletionSource<int>();
            Task<int> myTask = task.Task;       // task 控制 myTask

            // 新開一個任務(wù)做實驗
            Task mainTask = new Task(() =>
            {
                Console.WriteLine("我可以控制 myTask 任務(wù)");
                Console.WriteLine("按下任意鍵,我讓 myTask 任務(wù)立即完成");
                Console.ReadKey();
                task.SetResult(666);
            });
            mainTask.Start();

            Console.WriteLine("開始等待 myTask 返回結(jié)果");
            Console.WriteLine(myTask.Result);
            Console.WriteLine("結(jié)束");
            Console.ReadKey();
        }

其它例如 SetException(Exception) 等方法,可以自行探索,這里就不再贅述。

參考資料:https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/

這篇文章講得不錯,而且有圖:https://gigi.nullneuron.net/gigilabs/taskcompletionsource-by-example/

實現(xiàn)一個支持同步和異步任務(wù)的類型

這部分內(nèi)容對 TaskCompletionSource<TResult> 繼續(xù)進行講解。

這里我們來設(shè)計一個類似 Task 類型的類,支持同步和異步任務(wù)。

  • 用戶可以使用 GetResult() 同步獲取結(jié)果;
  • 用戶可以使用 RunAsync() 執(zhí)行任務(wù),使用 .Result 屬性異步獲取結(jié)果;

其實現(xiàn)如下:

/// <summary>
/// 實現(xiàn)同步任務(wù)和異步任務(wù)的類型
/// </summary>
/// <typeparam name="TResult"></typeparam>
public class MyTaskClass<TResult>
{
    private readonly TaskCompletionSource<TResult> source = new TaskCompletionSource<TResult>();
    private Task<TResult> task;
    // 保存用戶需要執(zhí)行的任務(wù)
    private Func<TResult> _func;

    // 是否已經(jīng)執(zhí)行完成,同步或異步執(zhí)行都行
    private bool isCompleted = false;
    // 任務(wù)執(zhí)行結(jié)果
    private TResult _result;

    /// <summary>
    /// 獲取執(zhí)行結(jié)果
    /// </summary>
    public TResult Result
    {
        get
        {
            if (isCompleted)
                return _result;
            else return task.Result;
        }
    }
    public MyTaskClass(Func<TResult> func)
    {
        _func = func;
        task = source.Task;
    }

    /// <summary>
    /// 同步方法獲取結(jié)果
    /// </summary>
    /// <returns></returns>
    public TResult GetResult()
    {
        _result = _func.Invoke();
        isCompleted = true;
        return _result;
    }

    /// <summary>
    /// 異步執(zhí)行任務(wù)
    /// </summary>
    public void RunAsync()
    {
        Task.Factory.StartNew(() =>
        {
            source.SetResult(_func.Invoke());
            isCompleted = true;
        });
    }
}

我們在 Main 方法中,創(chuàng)建任務(wù)示例:

    class Program
    {
        static void Main()
        {
            // 實例化任務(wù)類
            MyTaskClass<string> myTask1 = new MyTaskClass<string>(() =>
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                return "www.whuanle.cn";
            });

            // 直接同步獲取結(jié)果
            Console.WriteLine(myTask1.GetResult());


            // 實例化任務(wù)類
            MyTaskClass<string> myTask2 = new MyTaskClass<string>(() =>
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                return "www.whuanle.cn";
            });

            // 異步獲取結(jié)果
            myTask2.RunAsync();

            Console.WriteLine(myTask2.Result);


            Console.ReadKey();
        }
    }

Task.FromCanceled()

微軟文檔解釋:創(chuàng)建 Task,它因指定的取消標記進行的取消操作而完成。

這里筆者抄來了一個示例

var token = new CancellationToken(true);
Task task = Task.FromCanceled(token);
Task<int> genericTask = Task.FromCanceled<int>(token);

網(wǎng)上很多這樣的示例,但是,這個東西到底用來干嘛的?new 就行了?

帶著疑問我們來探究一下,來個示例:

        public static Task Test()
        {
            CancellationTokenSource source = new CancellationTokenSource();
            source.Cancel();
            return Task.FromCanceled<object>(source.Token);
        }
        static void Main()
        {
            var t = Test();	// 在此設(shè)置斷點,監(jiān)控變量
            Console.WriteLine(t.IsCanceled);
         }

Task.FromCanceled() 可以構(gòu)造一個被取消的任務(wù)。我找了很久,沒有找到很好的示例,如果一個任務(wù)在開始前就被取消,那么使用 Task.FromCanceled() 是很不錯的。

這里有很多示例可以參考:https://www.csharpcodi.com/csharp-examples/System.Threading.Tasks.Task.FromCanceled(System.Threading.CancellationToken)/

如何在內(nèi)部取消任務(wù)

之前我們討論過,使用 CancellationToken 取消令牌傳遞參數(shù),使任務(wù)取消。但是都是從外部傳遞的,這里來實現(xiàn)無需 CancellationToken 就能取消任務(wù)。

我們可以使用 CancellationToken 的 ThrowIfCancellationRequested() 方法拋出 System.OperationCanceledException 異常,然后終止任務(wù),任務(wù)會變成取消狀態(tài),不過任務(wù)需要先傳入一個令牌。

這里筆者來設(shè)計一個難一點的東西,一個可以按順序執(zhí)行多個任務(wù)的類。

示例如下:

    /// <summary>
    /// 能夠完成多個任務(wù)的異步類型
    /// </summary>
    public class MyTaskClass
    {
        private List<Action> _actions = new List<Action>();
        private CancellationTokenSource _source = new CancellationTokenSource();
        private CancellationTokenSource _sourceBak = new CancellationTokenSource();
        private Task _task;

        /// <summary>
        ///  添加一個任務(wù)
        /// </summary>
        /// <param name="action"></param>
        public void AddTask(Action action)
        {
            _actions.Add(action);
        }

        /// <summary>
        /// 開始執(zhí)行任務(wù)
        /// </summary>
        /// <returns></returns>
        public Task StartAsync()
        {
            // _ = new Task() 對本示例無效
            _task = Task.Factory.StartNew(() =>
             {
                 for (int i = 0; i < _actions.Count; i++)
                 {
                     int tmp = i;
                     Console.WriteLine($"第 {tmp} 個任務(wù)");
                     if (_source.Token.IsCancellationRequested)
                     {
                         Console.ForegroundColor = ConsoleColor.Red;
                         Console.WriteLine("任務(wù)已經(jīng)被取消");
                         Console.ForegroundColor = ConsoleColor.White;
                         _sourceBak.Cancel();
                         _sourceBak.Token.ThrowIfCancellationRequested();
                     }
                     _actions[tmp].Invoke();
                 }
             },_sourceBak.Token);
            return _task;
        }

        /// <summary>
        /// 取消任務(wù)
        /// </summary>
        /// <returns></returns>
        public Task Cancel()
        {
            _source.Cancel();

            // 這里可以省去
            _task = Task.FromCanceled<object>(_source.Token);
            return _task;
        }
    }

Main 方法中:

        static void Main()
        {
            // 實例化任務(wù)類
            MyTaskClass myTask = new MyTaskClass();

            for (int i = 0; i < 10; i++)
            {
                int tmp = i;
                myTask.AddTask(() =>
                {
                    Console.WriteLine("     任務(wù) 1 Start");
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                    Console.WriteLine("     任務(wù) 1 End");
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                });
            }

            // 相當于 Task.WhenAll()
            Task task = myTask.StartAsync();
            Thread.Sleep(TimeSpan.FromSeconds(1));
            Console.WriteLine($"任務(wù)是否被取消:{task.IsCanceled}");

            // 取消任務(wù)
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("按下任意鍵可以取消任務(wù)");
            Console.ForegroundColor = ConsoleColor.White;
            Console.ReadKey();

            var t = myTask.Cancel();    // 取消任務(wù)
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine($"任務(wù)是否被取消:【{task.IsCanceled}】");

            Console.ReadKey();
        }

你可以在任一階段取消任務(wù)。

Yield 關(guān)鍵字

迭代器關(guān)鍵字,使得數(shù)據(jù)不需要一次性返回,可以在需要的時候一條條迭代,這個也相當于異步。

迭代器方法運行到 yield return 語句時,會返回一個 expression,并保留當前在代碼中的位置。 下次調(diào)用迭代器函數(shù)時,將從該位置重新開始執(zhí)行。

可以使用 yield break 語句來終止迭代。

官方文檔:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/yield

網(wǎng)上的示例大多數(shù)都是 foreach 的,有些同學不理解這個到底是啥意思。筆者這里簡單說明一下。

我們也可以這樣寫一個示例:

這里已經(jīng)沒有 foreach 了。

        private static int[] list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        private static IEnumerable<int> ForAsync()
        {
            int i = 0;
            while (i < list.Length)
            {
                i++;
                yield return list[i];
            }
        }

但是,同學又問,這個 return 返回的對象 要實現(xiàn)這個 IEnumerable<T> 才行嘛?那些文檔說到什么迭代器接口什么的,又是什么東西呢?

我們可以先來改一下示例:

        private static int[] list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        private static IEnumerable<int> ForAsync()
        {
            int i = 0;
            while (i < list.Length)
            {
                int num = list[i];
                i++;
                yield return num;
            }
        }

你在 Main 方法中調(diào)用,看看是不是正常運行?

        static void Main()
        {
            foreach (var item in ForAsync())
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }

這樣說明了,yield return 返回的對象,并不需要實現(xiàn) IEnumerable<int> 方法。

其實 yield 是語法糖關(guān)鍵字,你只要在循環(huán)中調(diào)用它就行了。

        static void Main()
        {
            foreach (var item in ForAsync())
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }

        private static IEnumerable<int> ForAsync()
        {
            int i = 0;
            while (i < 100)
            {
                i++;
                yield return i;
            }
        }
    }

它會自動生成 IEnumerable<T> ,而不需要你先實現(xiàn) IEnumerable<T> 。

補充知識點

  • 線程同步有多種方法:臨界區(qū)(Critical Section)、互斥量(Mutex)、信號量(Semaphores)、事件(Event)、任務(wù)(Task);

  • Task.Run() 和 Task.Factory.StartNew() 封裝了 Task;

  • Task.Run()是 Task.Factory.StartNew() 的簡化形式;

  • 有些地方 net Task() 是無效的;但是 Task.Run() 和 Task.Factory.StartNew() 可以;

到此這篇關(guān)于C#多線程系列之任務(wù)基礎(chǔ)(三)的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#如何動態(tài)設(shè)置屏幕分辨率

    C#如何動態(tài)設(shè)置屏幕分辨率

    這篇文章主要為大家詳細介紹了C#動態(tài)設(shè)置屏幕分辨率的方法,我們可以使用Screen類設(shè)置屏幕分辨率,感興趣的小伙伴們可以參考一下
    2016-04-04
  • unity實現(xiàn)鼠標拖住3D物體

    unity實現(xiàn)鼠標拖住3D物體

    這篇文章主要為大家詳細介紹了unity實現(xiàn)鼠標拖住3D物體,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • C#中Dictionary的作用及用法講解

    C#中Dictionary的作用及用法講解

    這篇文章主要介紹了C#中Dictionary的作用及用法講解,本文還對dictionary類用什么接口實現(xiàn)、Dictionary的基本用法做了講解,需要的朋友可以參考下
    2014-10-10
  • C#與js實現(xiàn)去除textbox文本框里面重復(fù)記錄的方法

    C#與js實現(xiàn)去除textbox文本框里面重復(fù)記錄的方法

    這篇文章主要介紹了C#與js實現(xiàn)去除textbox文本框里面重復(fù)記錄的方法,很實用的功能,需要的朋友可以參考下
    2014-08-08
  • C#生成比較短的Token字符串

    C#生成比較短的Token字符串

    這篇文章介紹了C#生成Token字符串的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-06-06
  • 一文掌握C# ListView控件的用法和示例代碼

    一文掌握C# ListView控件的用法和示例代碼

    ListView控件提供了豐富的屬性和事件,可以用于實現(xiàn)各種各樣的表格視圖,包括帶有單元格編輯、排序和分組等功能,本文介紹了一些常見的?ListView?控件的用法和示例代碼,感興趣的朋友一起看看吧
    2024-02-02
  • 算法練習之從String.indexOf的模擬實現(xiàn)開始

    算法練習之從String.indexOf的模擬實現(xiàn)開始

    這篇文章主要介紹了算法練習從String.indexOf的模擬實現(xiàn)開始,需要的朋友可以參考下
    2014-12-12
  • C# 泛型深入理解介紹

    C# 泛型深入理解介紹

    在上一個專題中介紹了C#2.0 中引入泛型的原因以及有了泛型后所帶來的好處,然而上一專題相當于是介紹了泛型的一些基本知識的,對于泛型的性能為什么會比非泛型的性能高卻沒有給出理由,所以在這個專題就中將會介紹原因和一些關(guān)于泛型的其他知識
    2012-11-11
  • C#客戶端程序調(diào)用外部程序的3種實現(xiàn)方法

    C#客戶端程序調(diào)用外部程序的3種實現(xiàn)方法

    這篇文章主要給大家介紹了關(guān)于C#客戶端程序調(diào)用外部程序的3種實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2018-04-04
  • C#使用Resources資源文件

    C#使用Resources資源文件

    這篇文章介紹了C#使用Resources資源文件的方法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-06-06

最新評論