gpt4 book ai didi

c# - ConfigureAwait(false) 导致错误而不是死锁的情况

转载 作者:太空狗 更新时间:2023-10-29 22:36:15 26 4
gpt4 key购买 nike

假设我编写了一个依赖于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 Error: 0 : System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it. at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value) at System.Windows.Controls.ContentControl.set_Content(Object value) at WpfApplication1.MainWindow.ActionToProcessNewValue(String s) in C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:line 56 at MyLibrary1.ClassFromMyLibrary1.d__0.MoveNext() in C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:line 77 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at WpfApplication1.MainWindow.d__1.MoveNext() in C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:line 39 Exception thrown: 'System.InvalidOperationException' in WpfApplication1.exe

显然,错误的发生是因为我的库中的等待者丢弃了当前的 WPF 上下文。

另一方面,在删除库中所有位置的 ConfigureAwait(false) 后,我显然陷入了死锁。

There is more detailed example of code这解释了我必须处理的一些限制。

那么我该如何解决这个问题呢?这里最好的方法是什么?我是否仍需要遵循关于 ConfigureAwait 的最佳实践?

PS,在实际场景中,我有很多类和方法,因此我的库中有大量此类异步调用。几乎不可能找出某个特定的异步调用是否需要上下文(请参阅对@Alisson 回复的评论)来修复它。不过,我不关心性能,至少在这一点上是这样。我正在寻找一些通用方法来解决这个问题。

最佳答案

通常情况下,库会记录回调是否保证在调用它的同一线程上,如果没有记录,最安全的选择是假设它没有。您的代码示例(以及我从您的评论中可以看出的与您合作的第 3 方)属于“不保证”类别。在那种情况下,您只需要检查是否需要执行 Invoke从回调方法内部执行它,您可以调用 Dispatcher.CheckAccess()它将返回 false如果您需要在使用控件之前调用。

private async Task<string> ActionToProcessNewValue(string s)
{
//I like to put the work in a delegate so you don't need to type
// the same code for both if checks
Action work = () => Label1.Content = s;
if(Label1.Dispatcher.CheckAccess())
{
work();
}
else
{
var operation = Label1.Dispatcher.InvokeAsync(work, DispatcherPriority.Send);

//We likely don't need .ConfigureAwait(false) because we just proved
// we are not on the UI thread in the if check.
await operation.Task.ConfigureAwait(false);
}

return string.Format("test2{0}", s);
}

这是一个替代版本,使用同步回调而不是异步回调。

private string ActionToProcessNewValue(string s)
{
Action work = () => Label1.Content = s;
if(Label1.Dispatcher.CheckAccess())
{
work();
}
else
{
Label1.Dispatcher.Invoke(work, DispatcherPriority.Send);
}

return string.Format("test2{0}", s);
}

如果您想从 Label1.Content 中获取值,这是另一个版本而不是分配它,这也不需要在回调中使用异步/等待。

private Task<string> ActionToProcessNewValue(string s)
{
Func<string> work = () => Label1.Content.ToString();
if(Label1.Dispatcher.CheckAccess())
{
return Task.FromResult(work());
}
else
{
return Label1.Dispatcher.InvokeAsync(work, DispatcherPriority.Send).Task;
}
}

重要提示:如果您不删除 .Result,所有这些方法都会导致您的程序死锁。在按钮单击处理程序中,Dispatcher.InvokeDispatcher.InvokeAsync在等待 .Result 时回调永远不会开始返回并.Result在等待回调返回时永远不会返回。您必须将点击处理程序更改为 async void并做一个 await而不是 .Result .

关于c# - ConfigureAwait(false) 导致错误而不是死锁的情况,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39105998/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com