异步方法将来回控制来回切换到下一个异步方法

时间:2015-05-27 08:43:12

标签: c# asynchronous

我有两个异步方法从数据库中的两个表中检索数据。在运行时,我收到错误

  

收藏被修改;枚举操作可能无法执行

我进入我的代码并意识到一些奇怪的事情,即使在方法执行完毕之前,这两种方法也来回切换控制。方法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()); 

我的问题是我想要处理它究竟发生了什么。

3 个答案:

答案 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