我有两个异步方法从数据库中的两个表中检索数据。在运行时,我收到错误
收藏被修改;枚举操作可能无法执行
我进入我的代码并意识到一些奇怪的事情,即使在方法执行完毕之前,这两种方法也来回切换控制。方法1将执行,并且在两行或三行之后,方法2在几行控制返回到方法1之后执行。这发生直到我得到该异常。有趣的是,虽然发布这个,我运行应用程序,我没有异常。以下是两种方法
public async void InitializeParts()
{
populator = new Populator();
await Task.Run(()=> detailsDataTable = new DataTable());
string partSql = "SELECT * FROM tblparts";
await Task.Run(() => populator.FillDataTableAsync(partSql, detailsDataTable));
await Task.Run(() => partDetailsDataGridView.DataSource = detailsDataTable);
detailsCriteriaSearchBox.Text = string.Empty;
}
public async void InitializeTrack()
{
populator = new Populator();
await Task.Run(() => trackDataTable = new DataTable());
string TrackSql = "SELECT * FROM tblmodule";
await Task.Run(() => populator.FillDataTableAsync(TrackSql, trackDataTable));
await Task.Run(() => trackModulesDataGridView.DataSource = trackDataTable);
trackCriteriaSearchBox.Text = string.Empty;
}
我像这样一个接一个地调用这两个方法
await Task.Run(() => InitializeParts());
await Task.Run(() => InitializeTrack());
我的问题是我想要处理它究竟发生了什么。
答案 0 :(得分:3)
public async void InitializeParts()
这是一个不返回Task
的异步方法,因此无法等待。
await Task.Run(() => InitializeParts());
这会创建一个可以等待的Task
,然后调用不能的上述方法。因此,Task
将在InitializeParts()
开始后返回,但在等待InitializeParts()
完成后不会返回。 (最有可能在await
内的第一个InitializeParts()
之后。
将public async void InitializeParts()
更改为public async Task InitializeParts()
。
此外,当您执行以下操作时:
await Task.Run(() => populator.FillDataTableAsync(partSql, detailsDataTable));
然后,您正在创建一个调用另一个任务的任务,但不等待它,然后等待该“外部”任务。这应该只是:
await populator.FillDataTableAsync(partSql, detailsDataTable);
将await Task.Run(() => InitializeParts());
更改为await InitializeParts();
。
使用其他方法执行等效操作。
现在你将await
完成整个任务。
有趣的是,在发布此内容时,我运行了应用程序而没有异常。
您的问题是您的异步代码依赖于它的两个部分之间的顺序,并且您没有正确强制该顺序。没有什么可说的,它有时候不会恰好以正确的顺序运作,也没有任何东西可以保证它也会如此。
答案 1 :(得分:2)
您正在使用async void
,这基本上意味着您的来电:
await Task.Run(() => InitializeParts());
await Task.Run(() => InitializeTrack());
紧接着发生,因此InitializeTrack()
等待InitializeParts()
完成。
这是TPL的既定部分。
有关最佳做法的更多信息以及为什么通常应该避免异步无效已在MSDN https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
上结束您可以通过将它们更改为async Task
来解决此问题,然后等待它们完成即可。
答案 2 :(得分:2)
我的问题是究竟发生了什么
发生的事情是你使用多个线程池线程来执行数据库调用,并且因为你的方法被标记为async void
,你实际上没有await
在内部{{1}调用(及其生成的任务,因为你不能。这就是为什么你看到第一个和第二个“竞赛”相互完成。
您应该考虑采取不同的做法。
首先,最重要的是,请勿使用Task.Run
,始终使用async void
。前者只是为了兼容事件处理程序(这里不是这种情况),并导致你的异步方法处于“火上浇油”的状态,这显然是你不想要的。
其次,不要使用async over sync反模式。如果您的方法是同步的,并且您想在线程池线程上对其进行排队,请在最顶层的调用堆栈中执行此操作。不要公开async Task
方法,这会使调用者认为这个调用实际上是异步的(它不在这里):
XXXAsync
现在:
public void InitializeParts()
{
populator = new Populator();
detailsDataTable = new DataTable();
string partSql = "SELECT * FROM tblparts";
populator.FillDataTable(partSql, detailsDataTable);
partDetailsDataGridView.DataSource = detailsDataTable;
detailsCriteriaSearchBox.Text = string.Empty;
}
public void InitializeTrack()
{
populator = new Populator();
trackDataTable = new DataTable();
string TrackSql = "SELECT * FROM tblmodule";
populator.FillDataTable(TrackSql, trackDataTable);
trackModulesDataGridView.DataSource = trackDataTable;
trackCriteriaSearchBox.Text = string.Empty;
}
现在,当您查询数据库(这是异步IO调用)时,您根本不需要使用 await Task.Run(() => InitializeParts());
await Task.Run(() => InitializeTrack());
。您可以使用数据库提供程序公开的自然异步API:
Task.Run
现在,当你调用它们时,没有理由使用public async Task InitializePartsAsync()
{
populator = new Populator();
detailsDataTable = new DataTable();
string partSql = "SELECT * FROM tblparts";
await populator.FillDataTableAsync(partSql, detailsDataTable);
partDetailsDataGridView.DataSource = detailsDataTable;
detailsCriteriaSearchBox.Text = string.Empty;
}
public async Task InitializeTrackAsync()
{
populator = new Populator();
trackDataTable = new DataTable();
string TrackSql = "SELECT * FROM tblmodule";
await populator.FillDataTableAsync(TrackSql, trackDataTable);
trackModulesDataGridView.DataSource = trackDataTable;
trackCriteriaSearchBox.Text = string.Empty;
}
,因为主要的IO工作Task.Run
是异步完成的,不会占用任何线程:
FillDataTableAsync