[英]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.