我试图在我的应用程序上学习并实现async / await关键字。我使用API获取数据,然后在我的表单上显示它们。当我尝试从控制台应用程序调用方法时没有问题。但是,如果我从Form_Shown事件调用我的异步方法,也没有异常,但方法不起作用。
所以我在Form_Shown事件上调用了RefreshOrLoadDataToCache()方法。
private async void LogTimeReport_Shown(object sender, EventArgs e)
{
// Some syncronous operations
RefreshOrLoadDataToCache(); // Async methods in it
// Some syncronous operations
}
在我的方法中创建了一个任务并等待它。
private async void RefreshOrLoadDataToCache()
{
if (IsNeededToCallAPI())
{
var taskForTimeEntries = LoadTimeEntriesTemp();
Task.WhenAll(taskForTimeEntries);
DataTable dtTimeEntriesTemp = taskForTimeEntries.Result;
DataTable dtEventsTemp = LoadEventsTemp();
dtTimeEntriesTemp.Merge(dtEventsTemp);
}
else
BindGridViews();
}
这是我的异步方法。
private async Task<DataTable> LoadTimeEntriesTemp()
{
TimeEntryHandler timeHandler = new TimeEntryHandler();
TimeEntryResponse response = await timeHandler.GetTimeEntries();
DataTable dt = DatatableHelper.ToDataTable<TimeEntry>(response.TimeEntries);
foreach (DataRow drow in dt.Rows)
{
// Some operations on DataTable
}
return dt;
}
在这种方法中,我连接到API并获得结果。我认为我的问题是这个方法。因为当我从控制台应用程序调用此方法时,它返回数据。但是从表单应用程序开始,它会等待很长时间,但没有结果或例外。
private async Task<TimeEntryResponse> GetTimeEntries()
{
using (var client = new AuthorizedHttpClient(_client))
{
var data = await client.GetAsync<TimeEntryResponse>(parameters);
if (data.StatusCode == HttpStatusCode.OK)
{
var response = (TimeEntryResponse)data.ContentObj;
response.Pages = int.Parse(data.Headers.GetValues("X-Pages").First());
response.Page = int.Parse(data.Headers.GetValues("X-Page").First());
response.TotalRecords = int.Parse(data.Headers.GetValues("X-Records").First());
return response;
}
return new TimeEntryResponse() { TimeEntries = null, STATUS = "ERROR" };
}
}
我认为我对Windows窗体上的异步调用缺少一些东西。我该如何修复我的代码?
答案 0 :(得分:2)
您的代码存在一些问题
您将方法标记为async
,但您没有等待内部操作。您目前这样做是因为RefreshOrLoad
是async void
。它实际上需要是async Task
,其中底层返回的任务是正在进行的异步操作。然后,应该等待返回的Task
:
private async void LogTimeReport_Shown(object sender, EventArgs e)
{
// Some syncronous operations
await RefreshOrLoadDataToCache(); // Async methods in it
// Some syncronous operations
}
RefreshOrLoad
是一种异步方法。您使用Task.WhenAll
,它用于异步等待多个任务,但您也不会await
。然后,您拨打.Result
,which causes your code to effectively deadlock。所需要的只是等待从LoadTimeEntriesTemp
返回的任务:
private async Task RefreshOrLoadDataToCache()
{
if (IsNeededToCallAPI())
{
DataTable dtTimeEntriesTemp = await LoadTimeEntriesTemp();
DataTable dtEventsTemp = LoadEventsTemp();
dtTimeEntriesTemp.Merge(dtEventsTemp);
}
else
BindGridViews();
}
我还要注意,您应该使用异步方法使用*Async
后缀。
修复这些内容时,您会发现您的代码按预期运行,一直异步。
答案 1 :(得分:1)
你有问题:
var taskForTimeEntries = LoadTimeEntriesTemp();
Task.WhenAll(taskForTimeEntries);
DataTable dtTimeEntriesTemp = taskForTimeEntries.Result;
首先,为什么在只有一项任务时会使用Task.WhenAll
?然后泄漏Task.WhenAll
返回的任务,该任务将完成,并指示传递给Task.WhenAll
的所有任务都已完成。然后你等待同步任务导致死锁。
async / await有一个规则,即等待所有方式
所以正确的方法是:
DataTable dtTimeEntriesTemp = await LoadTimeEntriesTemp();
如果您想要执行与RefreshOrLoadDataToCache();
的结果相关的同步操作,您还应等待事件处理程序中的RefreshOrLoadDataToCache();
。
这是Stephen Cleary Don't Block on Async Code的一篇很棒的文章,它更详细地描述了你的问题。
答案 2 :(得分:0)
方法Task.WhenAll()
被标记为异步但它不使用等待LogTimeReport_Shown()
而private async void RefreshOrLoadDataToCache()
{
if (IsNeededToCallAPI())
{
var taskForTimeEntries = LoadTimeEntriesTemp();
DataTable dtTimeEntriesTemp = await taskForTimeEntries; // call await here
DataTable dtEventsTemp = LoadEventsTemp();
dtTimeEntriesTemp.Merge(dtEventsTemp);
}
else
BindGridViews();
}
不必是异步。 :
Index Name Value
1 A value A
2 B Value ...
3 A Value .....
4 C Value C...