Pan*_*los 0 c# wpf asynchronous task
我再一次重新研究了异步任务.无论我如何设置任务,我的应用程序一直受到UI冻结的影响.我有以下代码从网页下载字符串:
internal string DownloadString(string URL)
{
var result = LoadCompanyContracts(URL);
return result.Result;
}
internal async Task<string> LoadCompanyContracts(string URL)
{
Task<string> task2 = Task<string>.Factory.StartNew(() =>
{
for (int i = 0; i <= 10000000; i++) Console.WriteLine(i);
WebClient wc = new WebClient();
string tmp = wc.DownloadString(new Uri(URL));
return tmp;
});
return task2.Result;
}
Run Code Online (Sandbox Code Playgroud)
当我执行此任务时,在for循环期间,我的应用程序的UI正在冻结.即使我相信这段代码不应该冻结UI我也无法找到解决方案.我尝试了很多不同的选项,并且真的想使用任务而不是使用webclient异步的线程或事件.
信息:我正在为我的项目使用.net 4.5.我的代码的不同之处在于这些函数在类库中(不知道它是否重要).
是否可以通过从我的代码中调用DownloadString函数来运行此代码而不用异步await阻止用户界面?如果不是什么替代品(任何好的nuget包)?
该async关键字不使事情异步运行,它使您能够使用await到等待已异步操作.您需要使用
DownloadStringTaskAsync以异步方式真正下载:
internal async Task<string> LoadCompanyContracts(string URL)
{
....
using(var wc = new WebClient())
{
string tmp = await wc.DownloadStringTaskAsync(new Uri(URL));
return tmp;
}
}
Run Code Online (Sandbox Code Playgroud)
await本身返回原始执行上下文(即UI线程)中的执行.这可能是也可能不是,这就是为什么库代码通常使用ConfigureAwait(false);并让库的最终用户决定如何等待的原因:
string tmp = await wc.DownloadStringTaskAsync(new Uri(URL))
.ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)
最后,如果您打算.Result从顶级功能打电话,那么等待是没有意义的.有一个在使用毫无意义await可言,如果你不想做的使用方法的结果在你的代码.LoadCompanyContracts可能只是:
internal Task<string> LoadCompanyContracts(string URL)
{
....
using(var wc = new WebClient())
{
return wc.DownloadStringTaskAsync(new Uri(URL))
.ConfigureAwait(false);
}
}
Run Code Online (Sandbox Code Playgroud)
哎呀
通常,如果只返回异步操作的结果,则根本不需要使用await.该方法可能只是return wc.DownloadStringTaskAsync(..); 但会导致该方法返回和处置WebClient之前下载完成.避免using阻塞也不是解决方案,因为它会使像WebClient活着这样的昂贵对象超过必要时间.
这就是为什么HttpClient比以下更好WebClient:单个实例支持多个并发调用,这意味着您可以只有一个实例,例如作为字段并重用它,例如:
HttpClient _myClient =new HttpClient();
internal Task<string> LoadCompanyContractsAsync(string URL)
{
....
return _myClient.GetStringAsync(new Uri(URL))
.ConfigureAwait(false);
}
}
Run Code Online (Sandbox Code Playgroud)
你可以摆脱你,DownloadString因为它没有做任何事情LoadCompanyContracts.如果确实使用了结果LoadCompanyContracts,则应将其重写为:
internal async Task<string> DownloadString(string URL)
{
var result = await LoadCompanyContracts(URL);
//Do something with the result
return result;
}
Run Code Online (Sandbox Code Playgroud)
编辑
最初的答案使用了DownloadStringAsync,这是一种在下载完成时引发事件的遗留方法.正确的方法是DownloadStringTaskAsync
编辑2
由于我们讨论的是UI,因此可以通过使用处理程序的async void语法将代码一直异步到顶级事件处理程序async void Button1_Click,例如:
async void LoadCustomers_Click(...)
{
var contracts=await LoaCompanyContracts(_companyUrls);
txtContracts>Text=contracts;
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我们想要返回原始线程,所以我们不使用ConfigureAwait(false);