Предположим, я написал библиотеку, которая использует 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)
(как в этот пост) везде в моя библиотека. Затем я использую его синхронно из своего тестового приложения и получаю ошибку:
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(String s ) в C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml.cs:строка 56 в MyLibrary1.ClassFromMyLibrary1.d__0.MoveNext() в C:\dev\tests\4\WpfApplication1\WpfApplication1\MainWindow.xaml .cs: строка 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:строка 39 Исключение: ' System.Invalid OperationException» в WpfApplication1.exe
Очевидно, ошибка возникает из-за того, что ожидающие в моей библиотеке отбрасывают текущий контекст WPF.
С другой стороны, после удаления ConfigureAwait(false)
везде в библиотеке я, очевидно, вместо этого получаю тупик.
Есть более подробный пример кода, объясняющий некоторые ограничения, с которыми мне приходится иметь дело.
Итак, как я могу решить эту проблему? Каков наилучший подход здесь? Нужно ли мне по-прежнему следовать рекомендациям в отношении ConfigureAwait
?
PS. В реальном сценарии у меня много классов и методов, поэтому в моей библиотеке куча таких асинхронных вызовов. Почти невозможно выяснить, требует ли какой-либо конкретный асинхронный вызов контекст или нет (см. комментарии к ответу @Alisson), чтобы исправить это. Меня не волнует производительность, по крайней мере, на данный момент. Я ищу какой-то общий подход к решению этой проблемы.
ConfigureAwait(false)
отовсюду, единственное место, где вам нужно удалить этоawait GetValueByKey(key).ConfigureAwait(false);
, и это будет работать. Полное объяснение причин см. в ответе Allision. - person Scott Chamberlain   schedule 23.08.2016Task.Run(async () => c.MethodFromMyLibrary1)
и не ждать завершения. Но это вызывает другие серьезные проблемы, такие как обработка ошибок и возврат результата. Хм, это становится действительно сложным. - person neleus   schedule 23.08.2016