nehme ich eine Bibliothek geschrieben haben, die auf async
Methoden beruht:Ein Fall, wenn ConfigureAwait (false) statt Deadlock einen Fehler verursacht
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);
}
}
}
ich die Empfehlungen gefolgt ConfigureAwait(false)
der Verwendung (wie in this post) überall in meine Bibliothek. Dann benutze ich es in synchron Weg von meinem Test-App und einen Fehler erhalten:
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));
}
}
}
Der Fehler ist:
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
Offensichtlich ist der Fehler tritt auf, weil die awaiters in meiner Bibliothek aktuellen Kontext WPF verwerfen .
Auf der anderen Seite, nach dem Entfernen der ConfigureAwait(false)
überall in der Bibliothek bekomme ich offensichtlich eine Deadlock statt.
There is more detailed example of code was einige Einschränkungen erklärt, mit denen ich mich befassen muss.
Also, wie kann ich dieses Problem angehen? Was ist der beste Ansatz hier? Muss ich immer noch die Best Practice bezüglich ConfigureAwait
befolgen?
PS, im realen Szenario habe ich viele Klassen und Methoden daher Tonnen solcher async Anrufe in meiner Bibliothek. Es ist nahezu unmöglich herauszufinden, ob ein bestimmter Async-Aufruf Kontext erfordert oder nicht (siehe Kommentare zur @Alisson-Antwort), um ihn zu beheben. Die Leistung interessiert mich zumindest an dieser Stelle nicht. Ich suche nach einem allgemeinen Ansatz, um dieses Problem anzugehen.
Sie müssen 'ConfigureAwait (false)' nicht von überall löschen, der einzige Ort, den Sie entfernen müssen, ist 'erwarten GetValueByKey (key) .ConfigureAwait (false);' und es wird funktionieren. Siehe [Allisions Antwort] (http://stackoverflow.com/a/39106835/80274) für eine vollständige Erklärung warum. –
Ihre Anforderungen sind nachweislich unmöglich zu erfüllen. Sie möchten, dass Ihr UIhread dort nichts tut, und dass er nichts tun kann, bis Ihre Operation abgeschlossen ist. Gleichzeitig müssen Sie dieselbe Operation ausführen, um Code im UI-Thread auszuführen, bevor er abgeschlossen werden kann. Ihre Anforderungen * Mandat * eine Sackgasse. Eine dieser beiden Anforderungen muss entfernt werden, damit das Problem lösbar ist. – Servy
Sie haben Recht. Ich habe den Client-Code vereinfacht, um den Aufruf _synchronous_ zu demonstrieren. In realem Code kann ich nicht ändern, wie die Bibliotheksmethode aufgerufen wird, weil sie von der _synchronous_-Methode aufgerufen wird, die von einer anderen _synchronous_-Methode usw. aufgerufen wird. Diese Kette kommt schließlich vom GUI-Event-Handler. – neleus