我有一个通用应用程序,可以从Internet源更新多个对象。为了更新进度指示器并防止重复请求,我有以下逻辑。
在刷新按钮上:
foreach (var package in Packages.Where(item => !item.Received))
{
RefeshPackage(package);
}
为列表中的每个包调用RefreshPackage:
private async Task RefeshPackage(PackageModel package)
{
if (Tasks.Contains(package.Id)) return;
Tasks.Add(package.Id);
await DownloadAndUpdate(package);
Tasks.Remove(package.Id);
Refresh.RaiseCanExecuteChanged();
}
为每个包调用DownloadAndUpdate:
private async Task DownloadAndUpdate(PackageModel package)
{
var response = await webService.GetPackageStages(package.Id);
if (response != null)
{
switch (response.Status)
{
case 200:
package.UpdateStages(response.Stages);
break;
case 500:
//package doesn't exist or website down
break;
default:
break;
}
}
else
{
//no network / timed out
}
}
为每个包调用GetPackageStages:
public async Task<ResponseData> GetPackageStages(string id)
{
var requestUri = string.Concat(MyUri, id);
var client = new HttpClient();
client.Timeout = new TimeSpan(0,0,5);
try
{
var response = await client.GetAsync(requestUri);
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
//process response
}
catch (Exception e)
{
return null;
}
finally
{
client.Dispose();
}
}
挂起/死锁始终发生在最后一个函数中,在行中:
await client.GetAsync(requestUri);
有时候它永远挂在这里,有时候工作得很好。我已经阅读了很多关于死锁的其他答案,常见的解决方案是使用async / await一直(我已经在做)或使用 .ConfigureAwait(false)< / em>也到处都是(我已经没有成功)。
我还缺少其他东西,还是应该以不同的方式构建代码?我已经没有想法/解决方案了。任何帮助将不胜感激。
答案 0 :(得分:0)
外部foreach
只执行RefeshPackage
而不对其结果做任何事情。这种“即发即忘”方法的问题是异常将无法观察到,因此很难判断异步代码是否实际上正确执行。即使您的任务应该处理异常,您仍然应该观察它,以防错误处理策略未涵盖某些内容(在任务中使用catch-all处理程序与在其他任何地方使用它一样糟糕)。 / p>
有很多方法可以使用生成的Task
。哪个合适取决于您的具体情况。如果您的外部循环本身是async
,则await
结果就足够了(如果它是同步的,请使用Task.Wait()
)。这意味着在任务完成之前循环不会继续;如果您确实希望并行运行所有任务,请在Task[]
中收集它们并使用await Task.WhenAll()
(对于同步代码,请使用Task.WaitAll()
)。
如果您希望所有任务并行运行并且不关心任何结果,那么您可以使用Task.ContinueWith()
,通过TaskContinuationOptions.OnlyOnFaulted
来运行日志记录代码。这是最接近“发射并忘记”而不丢弃实际错误的东西。
一旦你修复了它并且可以观察到异步代码的任何错误,你就可以调试实际的问题。