我有一个Winform项目,在winform类中我有一个名为DataBindingTasks的属性,就像这样。
// create a task list to determine when tasks have finished during load
protected List<Task> DataBindingTasks = new List<Task>();
我在winform“Load”事件中调用了几个异步void方法,这些方法与以下内容类似。
private async void BindSomething(int millSecToWait)
{
var someTask = Task.Factory.StartNew(() =>
{
// do some work
System.Threading.Thread.Sleep(millSecToWait);
// return some list for binding
return new List<int>();
});
// add the task to the task list
DataBindingTasks.Add(someTask);
// wait until data has loaded
var listToBind = await someTask;
// bind the data to a grid
}
我在加载时调用BindSomething
方法
我说方法是因为有几种在加载时调用的绑定类型的方法。
private void Form_Load(object sender, EventArgs e)
{
// async bind something and let UI continue
// fire and forget
BindSomething(5000);
BindSomething(8000);
BindSomething(2000);
BindSomething(2000);
// code to execute when all data binding tasks have completed
Task.WhenAll(DataBindingTasks).ContinueWith((x) =>
{
// Do something after all async binding tasks have completed
});
}
除非所有任务都没有完成,否则ContinueWith
代码正在执行。
这是一个屏幕截图,显示所有任务都未完成。
更新10/29
问题显然比上面的示例代码更深,上面的示例代码并不能完全解释真实场景。
我会尝试更详细地解释,但尽量不要长久。
这是一个Winform应用程序
我们已经创建了一个基础winform“BaseForm”,所有其他winforms将继承
我们已经覆盖了“BaseForm”中的“OnLoad”事件,以便我们可以调用一个新方法,所有继承的表单都将调用“LoadData”。
由于“LoadData”可以具有异步方法调用,因此基本表单需要知道“LoadData”方法何时完成
所以在基本形式中有以下一些:
protected List<Task> DataBindingTasks = new List<Task>();
public event EventHandler DataBindingTasksComplete;
protected void OnDataBindingTasksComplete(EventArgs e)
{
if (DataBindingTasksComplete != null)
{
DataBindingTasksComplete(this, e);
}
// now clear the list
DataBindingTasks.Clear();
}
// NOTE: this is inside the OnLoad called before base.OnLoad(e)
Task.WhenAll(DataBindingTasks).ContinueWith((x) =>
{
OnDataBindingTasksComplete(EventArgs.Empty);
});
希望所有继承的表单都会将任何“异步”任务添加到此列表中,以便基本表单可以触发“DataBindingTasksComplete”事件,以便他们知道表单已完成加载。
“在问题发生时我们认为”这个问题是“WhenAll()。ContinueWith”没有等到列表上的所有任务完成。
但有人注意到,列表可能已经改变
所以这很有可能发生了什么
有4个“BindSomething”方法被标记为async,都是从Form_Load中调用的
“BindSomething”方法中的第二行左右用于将任务添加到“BaseForm.DataBindingTasks”列表中。
由于这些调用中的每一个都被标记为异步,因此Form_Load继续将所有4调用称为“发射并忘记”
之后,它返回到BaseForm OnLoad,然后查看“DataBindingTasks”列表以查看是否所有任务都已完成。
我最好的猜测是,其中一个“BindSomething”方法正在将其任务添加到列表中,但Base.OnLoad已经开始查看列表了。
我甚至可以在将“BindSomething”方法称为“占位符”之前将4个“假”任务(如线程休眠)添加到列表中,然后在“BindSomething”方法中将“假”任务换成“假”任务真正的“任务。” 这种接缝很乱,很可能会引起其他问题 最可能的解决方法是不使用任务列表/ WhenAll.ContinueWith,而是使用“await”调用加载数据,然后在下一行引发事件。
答案 0 :(得分:1)
async void
方法被称为fire-and-forget
,没有办法等待它们,这就是你的代表不能正常等待的原因 - 它根本无法做到。因此,您需要对代码进行一些更改。
更新:@Servy注意到我错过了您的代码中的主要问题,感谢他:
DataBindingTasks.Add(someTask);
此操作不是线程安全!在Add
方法的并行调用期间,您只是丢失了一些任务。您需要更改此项:使用lock
,使用ConcurrentCollection
或使用数据分离:通过不同的索引将任务分配给数组,以便并行任务不会相互交叉。
首先,在这种情况下,您不应使用StartNew
,请使用Task.Run
,否则使用can met some problems in your app。
第二件事是你可以使用Load
方法async
和await
,因此您的用户界面不会冻结,您可以切换{{1}的签名正如@digimunk所提到的那样,方法变得可以接受了:
BindSomething
在这种情况下,您可以安全地// note that we return the task here
private async Task BindSomething(int millSecToWait)
{
// use Task.Run in this case
var someTask = Task.Run(() =>
{
// Some work
System.Threading.Thread.Sleep(millSecToWait);
// return some list for binding
return new List<int>();
});
DataBindingTasks.Add(someTask);
// wait until data has loaded
var listToBind = await someTask;
// bind the data to a grid
}
// async void for the event handler
private async void Load()
{
// start tasks in fire-and-forget fashion
BindSomething(5000);
BindSomething(8000);
BindSomething(2000);
// code to execute when all data binding tasks have completed
await Task.WhenAll(DataBindingTasks);
// Do something after all binding is complete
}
await
方法。
答案 1 :(得分:-1)
你不需要.ContinueWith()。等待Task.WhenAll(),然后在它之后放置你想要运行的任何代码。另外,改变&#34; void&#34;在方法签名中&#34; async Task&#34;。