簡體   English   中英

我是否需要使用默認的 TaskScheduler 同步任務之間的資源訪問?

[英]Do I need to synchronize resource access between Tasks with the default TaskScheduler?

我正在使用 Tasks 和 await/async 進行編程。 我假設多線程的工作方式與 NodeJS 或 Python 中的一樣,也就是說,它不是,一切都在同一線程上運行。 但是我一直在嘗試了解 Tasks 是如何實際執行的,我的理解是它們是由 TaskScheduler.Default 執行的,它的實現是隱藏的,但可以預期使用 ThreadPool。

我應該像我的所有任務都可以在任何線程中運行一樣進行編程嗎?

我的異步編程的范圍是相當輕量級的 CPU 工作,由幾個無限循環組成,這些循環工作然后在 Task.Delay 上等待幾秒鍾。 現在唯一的共享資源是一個 int,每次我寫網絡消息時它都會增加,但將來我希望我的任務將是共享字典和列表。

我還有一個網絡任務,它連接到 TCP 服務器並使用我在 BeginRead+EndRead 上實現的任務讀取消息。 Read 函數由一個無限循環調用,該循環讀取消息、處理它,然后讀取一條新消息。

        void OnRead(IAsyncResult result)
        {
            var pair = (Tuple<TaskCompletionSource<int>, NetworkStream>)result.AsyncState;
            int count = pair.Item2.EndRead(result);
            pair.Item1.SetResult(count);
        }

        async Task<byte[]> Read(NetworkStream stream, uint size)
        {
            var result = new byte[size];
            var count = 0;
            while(count < size)
            {
                var tcs = new TaskCompletionSource<int>();
                stream.BeginRead(result, count, result.Length - (int)count, new AsyncCallback(OnRead), Tuple.Create(tcs, stream));
                count += await tcs.Task;
            }
            return result;
        }

我使用同步寫入寫入 NetworkStream。

我假設多線程的工作方式與 NodeJS 或 Python 中的一樣,也就是說,它不是,一切都在同一線程上運行。 但是我一直在嘗試了解 Tasks 是如何實際執行的,我的理解是它們是由 TaskScheduler.Default 執行的,它的實現是隱藏的,但可以預期使用 ThreadPool。

不完全是。

首先,.NET 中的Task可以是兩個完全不同的東西 委托任務代表可以在某個線程上運行的代碼,使用TaskScheduler來確定它們在何處以及如何運行。 委托任務是在原始任務並行庫中引入的,幾乎從未與異步代碼一起使用。 另一種Task是承諾任務。 它們更類似於 JavaScript 中的Promise :它們可以表示任何東西——它們只是一個“尚未完成”或“以結果完成”或“以錯誤完成”的對象。 這是不同類型任務的不同狀態圖的對比。

因此,首先要認識到的是,就像您不在 JavaScript 中“執行 Promise”一樣,您在 .NET 中也不“執行(Promise)任務”。 所以詢問它在哪個線程上運行是沒有意義的,因為它們不在任何地方運行

但是,JS 和 C# 都有async / await語言結構,允許您編寫更自然的代碼來控制承諾。 async方法完成時,promise 完成; 如果async方法拋出,則承諾失敗。

那么問題就變成了:控制這個承諾的代碼在哪里運行?

在 JavaScript 世界中,答案是顯而易見的:只有一個線程,所以代碼運行在那里。 在 .NET 世界中,答案有點復雜。 我的異步介紹給出了核心概念:每個async方法都開始在調用線程上同步執行,就像任何其他方法一樣。 當它由於awaitawait ,它將捕獲其“上下文”。 然后,當該async方法准備好在await之后恢復時,它會在該“上下文”中恢復。

“上下文”是SynchronizationContext.Current ,除非它是null ,在這種情況下上下文是TaskScheduler.Current 在現代代碼中,“上下文”通常是 GUI 線程上下文(始終在 GUI 線程上恢復)或線程池上下文(在任何可用的線程池線程上恢復)。

我應該像我的所有任務都可以在任何線程中運行一樣進行編程嗎?

如果在沒有上下文的情況下調用async方法中的代碼,則它可以在線程池線程上恢復。

我需要在任務之間同步資源訪問嗎

可能不是。 asyncawait關鍵字旨在允許輕松編寫串行代碼。 所以沒有必要將await之前的代碼與await之后的代碼同步; await之后的代碼將始終await之前的代碼之后運行,即使它運行在不同的線程上。 此外, await注入了所有必要的線程屏障,因此不存在亂序讀取或類似問題。

但是,如果您的代碼同時運行多個async方法,並且這些方法共享數據,則需要同步。 我有一篇博客文章介紹了這種意外的隱式並行(在文章末尾)。 一般來說,異步代碼鼓勵返回結果而不是應用副作用,只要你這樣做,隱式並行性就不是問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM