【发布时间】:2016-08-23 16:05:43
【问题描述】:
假设我编写了一个依赖于async 方法的库:
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));
}
}
}
失败是:
WpfApplication1.vshost.exe 错误:0: System.InvalidOperationException:调用线程无法访问 这个对象,因为不同的线程拥有它。在 System.Windows.Threading.Dispatcher.VerifyAccess() 在 System.Windows.DependencyObject.SetValue(DependencyProperty dp,对象 值)在 System.Windows.Controls.ContentControl.set_Content(对象 值)在 WpfApplication1.MainWindow.ActionToProcessNewValue(字符串 s) 在 C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:line 56 在 MyLibrary1.ClassFromMyLibrary1.d__0.MoveNext() 在 C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:line 77 --- 从先前抛出异常的位置结束堆栈跟踪 --- 在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务 任务)在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务 任务)在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() 在 WpfApplication1.MainWindow.d__1.MoveNext() 中 C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:line 39 抛出异常:“System.InvalidOperationException”在 WpfApplication1.exe
很明显,错误发生是因为我的库中的等待者丢弃了当前的 WPF 上下文。
另一方面,在库中的所有位置删除 ConfigureAwait(false) 后,我显然会陷入死锁。
There is more detailed example of code 解释了我必须处理的一些限制。
那么我该如何解决这个问题呢?这里最好的方法是什么?我还需要遵循有关ConfigureAwait 的最佳做法吗?
PS,在实际场景中,我有很多类和方法,因此我的库中有大量此类异步调用。几乎不可能找出某个特定的异步调用是否需要上下文(参见 cmets 对 @Alisson 的响应)来修复它。不过,至少在这一点上,我并不关心性能。我正在寻找一些通用的方法来解决这个问题。
【问题讨论】:
-
你不需要从任何地方删除
ConfigureAwait(false),你需要删除它的唯一地方是await GetValueByKey(key).ConfigureAwait(false);,它会起作用。有关原因的完整说明,请参阅 Allision's answer。 -
您的要求可能无法满足。您希望您的 UI 线程坐在那里什么都不做,在您的操作完成之前禁止它做任何事情,同时还需要在 UI 线程上执行一些代码才能完成相同的操作。您的要求要求陷入僵局。这两个要求之一将需要消失才能解决问题。
-
你是对的。我已经简化了客户端代码来演示 同步 调用。在实际代码中,我无法更改调用库方法的方式,因为它是从 同步 方法调用的,而该方法又被其他 同步 方法调用,依此类推。最终这条链来自 GUI 事件处理程序。
-
我唯一能做的就是
Task.Run(async () => c.MethodFromMyLibrary1),不要等待完成。但这会导致其他严重问题,例如错误处理和返回结果。嗯,这真的很复杂。 -
为了更好地理解我的要求,看看这个详细的代码示例dotnetfiddle.net/I4VBJs
标签: c# .net async-await synchronizationcontext