namespace MyLibrary1
{
public class ClassFromMyLibrary1
{
public async Task<string> MethodFromMyLibrary1(string key, Func<string, Task<string>> actionToProcessNewValue)
{
var remoteValue = await GetValueByKey(key).ConfigureAwait(false);
//do some transformations of the value
var newValue = string.Format("Remote-{0}", remoteValue);
var processedValue = await actionToProcessNewValue(newValue).ConfigureAwait(false);
return string.Format("Processed-{0}", processedValue);
}
private async Task<string> GetValueByKey(string key)
{
//simulate time-consuming operation
await Task.Delay(500).ConfigureAwait(false);
return string.Format("ValueFromRemoteLocationBy{0}", key);
}
}
}
我遵循使用 ConfigureAwait(false)
的建议(如this文章中所述),在我的库中的所有地方都使用它。然后我从我的测试应用程序以同步方式使用它,但却失败了:
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button1_OnClick(object sender, RoutedEventArgs e)
{
try
{
var c = new ClassFromMyLibrary1();
var v1 = c.MethodFromMyLibrary1("test1", ActionToProcessNewValue).Result;
Label2.Content = v1;
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError("{0}", ex);
throw;
}
}
private Task<string> ActionToProcessNewValue(string s)
{
Label1.Content = s;
return Task.FromResult(string.Format("test2{0}", s));
}
}
}
失败原因是:
显然,我的库中的等待者丢弃了当前的WPF上下文,因此出现了错误。WpfApplication1.vshost.exe错误: 0: System.InvalidOperationException:调用线程无法访问此对象,因为不同的线程拥有它。 在System.Windows.Threading.Dispatcher.VerifyAccess()中 在System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)中 在System.Windows.Controls.ContentControl.set_Content(Object value)中 在C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:line 56的MainWindow.ActionToProcessNewValue(String s)中 在C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:line 77的MyLibrary1.ClassFromMyLibrary1.d__0.MoveNext()中 --- 在上一个位置引发异常的堆栈跟踪结束 --- 在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)中 在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)中 在System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()中 在C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:line 39的MainWindow.d__1.MoveNext()中
另一方面,如果我在库中的每个地方都删除ConfigureAwait(false),显然会导致死锁。
这里有一个更详细的代码示例,解释了我必须处理的一些限制。
那么我该如何解决这个问题?在这里最好的方法是什么?我是否仍需要遵循关于ConfigureAwait的最佳实践?
PS,在真实场景中,我有许多类和方法,因此我的库中有大量这样的异步调用。找出某个特定的异步调用是否需要上下文以进行修复几乎是不可能的(请参见对@Alisson响应的评论)。虽然我不关心性能,但至少在这一点上,我正在寻找一些通用的方法来解决这个问题。
ConfigureAwait(false)
,唯一需要删除它的地方是await GetValueByKey(key).ConfigureAwait(false);
,这样就可以正常工作了。详细解释请参见 Allision 的回答。 - Scott ChamberlainTask.Run(async () => c.MethodFromMyLibrary1)
并且不等待完成。但这会引起其他严重问题,如错误处理和返回结果。嗯,这变得非常复杂。 - neleus