[英]Running a long-running parallel task in the background, while allowing small async tasks to update the foreground
我有大約 10 000 000 個任務,每個任務需要 1-10 秒才能完成。 我在一個強大的服務器上運行這些任務,使用 50 個不同的線程,每個線程選擇第一個未完成的任務,運行它,然后重復。
偽代碼:
for i = 0 to 50:
run a new thread:
while True:
task = first available task
if no available tasks: exit thread
run task
使用此代碼,我可以在任何給定數量的線程上並行運行所有任務。
實際上,代碼使用 C# 的 Task.WhenAll,如下所示:
ServicePointManager.DefaultConnectionLimit = threadCount; //Allow more HTTP request simultaneously
var currentIndex = -1;
var threads = new List<Task>(); //List of threads
for (int i = 0; i < threadCount; i++) //Generate the threads
{
var wc = CreateWebClient();
threads.Add(Task.Run(() =>
{
while (true) //Each thread should loop, picking the first available task, and executing it.
{
var index = Interlocked.Increment(ref currentIndex);
if (index >= tasks.Count) break;
var task = tasks[index];
RunTask(conn, wc, task, port);
}
}));
}
await Task.WhenAll(threads);
這就像我想要的那樣工作,但我有一個問題:由於這段代碼需要很多時間來運行,我希望用戶看到一些進展。 進度顯示在彩色位圖(代表矩陣)中,生成也需要一些時間(幾秒鍾)。
因此,我想在后台線程上生成此可視化。 但是這個其他后台線程永遠不會執行。 我懷疑它與並行代碼使用相同的線程池,因此被排隊,並且在並行代碼實際完成之前不會執行。 (這有點太晚了。)
這是我如何生成進度可視化的示例:
private async void Refresh_Button_Clicked(object sender, RoutedEventArgs e)
{
var bitmap = await Task.Run(() => // <<< This task is never executed!
{
//bla, bla, various database calls, and generating a relatively large bitmap
});
//Convert the bitmap into a WPF image, and update the GUI
VisualizationImage = BitmapToImageSource(bitmap);
}
那么,我怎樣才能最好地解決這個問題呢? 我可以創建一個Task
列表,其中每個Task
代表我的一個任務,並使用 Parallel.Invoke 運行它們,然后選擇另一個線程池(我認為)。 但隨后我必須生成 1000 萬個Task
對象,而不僅僅是 50 個Task
對象,運行我要做的一系列事情。 聽起來它使用的 RAM 比必要的多得多。 對此有什么聰明的解決方案嗎?
編輯:正如 Panagiotis Kanavos 在他的評論中建議的那樣,我嘗試用 ActionBlock 替換我的一些循環邏輯,如下所示:
// Create an ActionBlock<int> that performs some work.
var workerBlock = new ActionBlock<ZoneTask>(
t =>
{
var wc = CreateWebClient(); //This probably generates some unnecessary overhead, but that's a problem I can solve later.
RunTask(conn, wc, t, port);
},
// Specify a maximum degree of parallelism.
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = threadCount
});
foreach (var t in tasks) //Note: the objects in the tasks array are not Task objects
workerBlock.Post(t);
workerBlock.Complete();
await workerBlock.Completion;
注意:RunTask 只是使用 WebClient 執行 Web 請求,並解析結果。 里面沒有任何東西可以造成死鎖。
這似乎像舊的並行代碼一樣工作,只是它需要一兩分鍾來執行初始 foreach 循環來發布任務。 這種延遲真的值得嗎?
盡管如此,我的進度任務似乎仍然被阻止。 暫時忽略 Progress<T> 建議,因為這個簡化的代碼仍然遇到同樣的問題:
private async void Refresh_Button_Clicked(object sender, RoutedEventArgs e)
{
Debug.WriteLine("This happens");
var bitmap = await Task.Run(() =>
{
Debug.WriteLine("This does not!");
//Still doing some work here, so it's not optimized away.
};
VisualizationImage = BitmapToImageSource(bitmap);
}
因此,只要並行任務正在運行,它看起來仍然不會執行新任務。 我什至將“MaxDegreeOfParallelism”從 50 減少到 5(在 24 核服務器上)以查看 Peter Ritchie 的建議是否正確,但沒有改變。 還有其他建議嗎?
另一個編輯:
問題似乎是我用所有同時阻塞的 I/O 調用使線程池過載。 我用 HttpClient 及其異步函數替換了 WebClient,現在一切似乎都運行良好。
感謝大家的好建議! 盡管並非所有人都直接解決了問題,但我相信他們都改進了我的代碼。 :)
.NET 已經提供了一種機制來報告IProgress<T>和Progress<T>實現的進度。
IProgress 接口允許客戶端使用Report(T)類發布消息,而不必擔心線程。 該實現確保在適當的線程(例如 UI 線程)中處理消息。 通過使用簡單的IProgress< T>
接口,后台方法與處理消息的人分離。
您可以在 4.5中的異步:在異步 API 中啟用進度和取消一文中找到更多信息。 取消和進度 API 並非特定於 TPL。 即使對於原始線程,它們也可用於簡化取消和報告。
Progress< T> 在創建它的線程上處理消息。 這可以通過在實例化類時傳遞處理委托或通過訂閱事件來完成。 從文章復制:
private async void Start_Button_Click(object sender, RoutedEventArgs e)
{
//construct Progress<T>, passing ReportProgress as the Action<T>
var progressIndicator = new Progress<int>(ReportProgress);
//call async method
int uploads=await UploadPicturesAsync(GenerateTestImages(), progressIndicator);
}
其中ReportProgress
是一種接受 int 參數的方法。 它還可以接受一個復雜的類,報告已完成的工作、消息等。
異步方法只需要使用 IProgress.Report,例如:
async Task<int> UploadPicturesAsync(List<Image> imageList, IProgress<int> progress)
{
int totalCount = imageList.Count;
int processCount = await Task.Run<int>(() =>
{
int tempCount = 0;
foreach (var image in imageList)
{
//await the processing and uploading logic here
int processed = await UploadAndProcessAsync(image);
if (progress != null)
{
progress.Report((tempCount * 100 / totalCount));
}
tempCount++;
}
return tempCount;
});
return processCount;
}
這將后台方法與接收和處理進度消息的人分離。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.