我正在更新一个C# WPF项目,该项目以前使用WCF服务,现在调用基于REST的API。
以下代码是REST API调用的典型示例:
public static async Task<List<KBase>> GetKbase(int shopID)
{
// set request
string uri = ROOT_URI + "kbase/" + shopID.ToString();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", access_token.token);
// get
HttpResponseMessage response = await http_client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string result = response.Content.ReadAsStringAsync().Result;
List<KBase> kb = JsonConvert.DeserializeObject<List<KBase>>(result);
return kb;
}
else
{
string result = response.Content.ReadAsStringAsync().Result;
gFunc.AddLogEntry("Get Kbase: " + result);
}
return null;
}
如果我使用下面的代码调用上面的API方法,一切都会按预期进行。消息框显示true。
private async void btnTest_MouseUp(object sender, MouseButtonEventArgs e)
{
List<Models.API.KBase> kb = await apiSamadhi.GetKbase(1);
bool is_data = !(kb == null);
MessageBox.Show(is_data.ToString());
}
然而,在程序的许多部分,我使用后台工作程序来保持主UI线程的响应性。我创建了一个后台工作者,如下所示:
BackgroundWorker bgw;
List<Models.API.KBase> kb;
private void InitBGW()
{
bgw = new BackgroundWorker();
bgw.DoWork += bgw_DoWork;
bgw.RunWorkerCompleted += bgw_WorkCompleted;
}
下面的代码显示了DoWork和WorkCompleted方法:
private async void bgw_DoWork(object sender, DoWorkEventArgs e)
{
kbase = await apiSamadhi.GetKbase(1);
}
private void bgw_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
bool is_data = !(kbase == null);
MessageBox.Show(is_data.ToString());
System.Threading.Thread.Sleep(1000);
is_data = !(kbase == null);
MessageBox.Show(is_data.ToString());
}
当bgw_WorkCompleted运行时,kbase为null,第一个消息框显示false。然后在线程休眠一秒钟后,kbase不为空,第二个消息框显示true。
这不是我所期望的行为。这就像bgw_DoWork中的await不起作用。为什么bgw_WorkCompleted在对API的“await”调用完成之前运行?
我是错过了什么还是做了什么蠢事?我需要能够运行'等待'ed非UI线程上的API调用/任务,并能够更新UI时,任务完成。
尽管按照建议的链接和阅读更多的后台工作人员,异步和等待我仍然努力得到一个工作的解决方案。
考虑到后台工作人员的年龄越来越大,并且不建议沿着使用Async和Await,我尝试使用Task.Run方法。然而,所谓的“等待”任务在到达方法的结尾之前仍然没有完成。
在下面的代码中,id是一个静态通用帮助类中的全局int变量。
static public async void ReloadData()
{
id = 0;
await Task.Run(() => InitialiseDaysheet());
MessageBox.Show(id.ToString());
await Task.Run(() => LoadDaysheet());
MessageBox.Show(id.ToString());
// update ui after loading data
}
当上面的方法运行时,第一个消息框显示“0”,尽管InitialiseDaysheet()末尾的代码分配了50的值。第二个消息框显示“50”,但LoadDaysheet()的末尾分配了值100。
InitialiseDaysheet()和LoadDaysheet有很多代码,但在每个方法中都有各种API调用,如下所示。
static private async void InitialiseDaysheet()
{
// code
kb = await apiSamadhi.GetKbase(1);
// code
}
此外,如下所示添加ConfigureAwait(false)没有区别。
static private async void InitialiseDaysheet()
{
// code
kb = await apiSamadhi.GetKbase(1).ConfigureAwait(false);
// code
}
1条答案
按热度按时间iyzzxitl1#
BackgroundWorker
期望DoWork
处理程序被阻塞,但是在async void
的情况下,它会在实际的BRAC操作完成之前退出,并调用WorkCompleted
处理程序-这里的真实的工作只是启动BRAC操作,而不是等待它完成。BackgroundWorker
不能很好地与async
/await
配合使用。你可以完全删除
BackgroundWorker
,像这样的东西应该可以做到:此外,由于我们这里有一个GUI应用程序,它将有一个
SynchronizationContext
,这意味着所有与UI无关的代码都应该使用ConfigureAwait(false)
,以避免返回UI线程:等待后与UI交互时不需要添加
ConfigureAwait
:正如有人在评论中提到的,当你已经在
async
方法中时,没有理由调用Task<T>.Result
:如果忘记了
ConfigureAwait
,您可以返回到UI线程,.Result
调用将简单地阻止它。最后要注意的是--您并不总是需要将所有内容都 Package 在
Task.Run()
中--仅当Task.Run()
操作具有长时间执行的同步前缀时,这里可能不是这种情况--您可以使用如果您谨慎使用
ConfigureAwait()
且不在UI线程上调用Task<T>.Result
,将方法声明为
async void
明确表示它不能等待-您需要一些可等待的东西,如Task
,以便能够跟踪方法完成:如前所述,
Task.Run()
不是必需的-它用于更接近地模拟BackgroundWorker
的行为。async void
通常与事件处理程序一起使用,因为async
不会更改方法签名:ConfigureAwait(false)
与等待调用的能力无关-它只影响任务继续运行的线程的选择: