c# - 为什么在涉及异常处理时异步等待非常慢并且阻塞?
问题描述
private void buttonLoad_Click(object sender, EventArgs e)
{
DataTable dt = new DataTable(); //create datatable with 6000 records
dt.Columns.Add("Name");
dt.Columns.Add("Value");
for (int i = 0; i < 2000; i++)
{
dt.Rows.Add("Tim", "955");
dt.Rows.Add("Rob", "511");
dt.Rows.Add("Steve", "201");
}
dataGridView1.DataSource = dt;
}
private async void btnSend_Click(object sender, EventArgs e)
{
progressBar1.Minimum = 1;
progressBar1.Maximum = dataGridView1.Rows.Count;
progressBar1.Value = 1;
progressBar1.Step = 1;
List<Task> lstTasks = new List<Task>();
DataTable dt = (DataTable)dataGridView1.DataSource;
System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();
s.Start();
foreach (DataRow dr in dt.Rows)
{
lstTasks.Add(CallAPIAsync(dr));
}
await Task.WhenAll(lstTasks);
MessageBox.Show(s.ElapsedMilliseconds.ToString());
}
private async Task CallAPIAsync(DataRow dr)
{
try
{
await Task.Delay(2000); //simulate post api request that will pass dr[name] and dr[value]
if (new Random().Next(0,100)>95) //simulate error in the above request
{
throw new Exception("Test!");
}
}
catch (Exception e)
{
Thread.Sleep(1);//similate sync processing that takes 1ms
}
progressBar1.PerformStep();
}
在buttonLoad_Click
我将示例数据加载到数据表中。
在btnSend_Click
我模拟一个异步任务。
在异步任务中,如果您更改
if (new Random().Next(0,100)>95)
至
if (new Random().Next(0,100)>5)
为了模拟更多的异常,即使catch块只需要1ms,代码也会运行得很慢。
为什么在涉及异常处理时异步等待非常慢并且阻塞?
解决方案
虽然评论中已经有一些很好的提示,但我发现了一些让我绊倒的点:
您并行运行 2000 个(或您的评论 6000 个)任务。由于我们在 Winforms 中(WPF 也一样),这些任务中的每一个都将 UI 线程作为同步上下文,这意味着即使你说Task.WhenAll()
,它们都必须按顺序执行,因为它们运行在 UI 线程中。
然后在你的代码中你有这个new Random().Next()
。这意味着创建了一个新的随机实例,并从当前时间生成种子。这导致了这样一个事实,即您多次产生相同的随机数。当这个数字在您的 95 - 100 范围内时,所有这些任务都会导致Thread.Sleep(1)
(而不是await Task.Delay(1)
),并且由于您在 UI 线程中,您的 UI 将冻结。
所以在这里我对你的改进:
从ui upate 代码中分解你的工作马。当您使用时,代码将在另一个线程中执行,但您不能简单地编写,您必须将其包装在调用中以将该方法分派到 UI 线程。
CallAPIAsync(dr).ConfigureAwait(false)
progressBar1.PerformStep()
progressBar1.BeginInvoke()
当您在任务世界中时,请不要使用
Thread.Sleep()
,因为一个线程负责多个任务。而是使用await Task.Delay()
,以便同一线程中的其他任务可以完成他们的工作。请注意
async / await
UI 应用程序中 using 的含义,以及您的代码是否将在 UI 线程或其他地方运行。了解如何.ConfigureAwait(false)
在这些情况下正确使用。学习正确用法
new Random()
。
另外,你能告诉我每个回调是否在运行下一个回调之前完全运行吗?
这个问题有点复杂,不适合评论。所以这是我的答案。
在您当前的实现中,由于缺少ConfigureAwait(false)
. 所以你所有的任务都必须由 UI 线程处理。它们按顺序开始,直到到达您的第一个Task.Delay(2000)
. 在这里,他们排队等待在两秒钟内进行处理。因为排队 2000 个任务比两秒快,你的所有任务或多或少并行地到达这一点。延迟结束后,它们必须由唯一的 UI 线程再次处理。因此它创建一个新Random
实例,调用 next 并根据整个过程的结果(注意:UI)线程冻结一毫秒或不冻结。由于您对 Random 类的滥用,您可能会遇到很多异常,如果所有 2000 个任务在 1 毫秒内都遇到异常,这会使您的 UI 冻结 2 秒。
推荐阅读
- mvvm - 如何将 isLoading 与 viewModel 和环境对象一起使用
- java - 向正则表达式过滤器制造商寻求一个好的java“完整路径glob”
- python - 当我有 F(x,y)=0 并包括 x 和 y 的不确定性时绘制 xy
- vba - 修改宏以将 DOS 文档内嵌脚注转换为 MS Word 页面底部脚注
- elm - 更新 Elm 中的数据值
- javascript - 从同一域但不同文件夹中的另一个页面加载部分内容( html 和 css )
- flutter - Flutter:如何创建两列布局,每列布局具有不同大小的可滚动 ListView
- java - Lombok ArrayObject 初始化
- amazon-web-services - 防止从目标组中删除不正常的目标 AWS LoadBalancer
- c++ - 为什么选择二元运算符而不是一元运算符?