Я изучаю async / await и столкнулся с ситуацией, когда мне нужно синхронно вызывать асинхронный метод. Как я могу это сделать?
Асинхронный метод:
public async Task<Customers> GetCustomers()
{
return await Service.GetCustomersAsync();
}
Нормальное использование:
public async void GetCustomers()
{
customerList = await GetCustomers();
}
Я пытался использовать следующее:
Task<Customer> task = GetCustomers();
task.Wait()
Task<Customer> task = GetCustomers();
task.RunSynchronously();
Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)
Я также попробовал предложение отсюда , однако оно не работает, когда диспетчер находится в приостановленном состоянии.
public static void WaitWithPumping(this Task task)
{
if (task == null) throw new ArgumentNullException(“task”);
var nestedFrame = new DispatcherFrame();
task.ContinueWith(_ => nestedFrame.Continue = false);
Dispatcher.PushFrame(nestedFrame);
task.Wait();
}
Вот исключение и трассировка стека от вызова RunSynchronously
:
System.InvalidOperationException
Сообщение : RunSynchronously не может быть вызван для задачи, не связанной с делегатом.
InnerException : ноль
Источник : mscorlib
StackTrace :
at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
at System.Threading.Tasks.Task.RunSynchronously()
at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
at System.Collections.Generic.List`1.ForEach(Action`1 action)
at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at System.Threading.ExecutionContext.runTryCode(Object userData)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.Run()
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
c#
asynchronous
c#-5.0
async-await
Рейчел
источник
источник
Task
синхронное использование ; 3) Я интегрирую GeneXus с драйвером MongoDB C # , который предоставляет некоторые методы только асинхронноSemaphoreSlim
.Ответы:
Вот обходной путь, который я нашел, который работает для всех случаев (включая приостановленных диспетчеров). Это не мой код, и я все еще работаю над его полным пониманием, но он работает.
Это можно вызвать используя:
customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());
Код отсюда
источник
DynamicNodeProviderBase
базовый класс, он не может объявить его какasync
метод. Либо мне пришлось заменить новую библиотеку, либо просто вызвать синхронную операцию.AspNetSynchronizationContext
, поэтому этот конкретный хак не будет работать, если вы вызываете эти API-интерфейсы.Имейте в виду, что этому ответу три года. Я написал это, основываясь в основном на опыте с .Net 4.0, и очень мало на 4.5, особенно на
async-await
. Вообще говоря, это хорошее простое решение, но оно иногда ломает вещи. Пожалуйста, прочитайте обсуждение в комментариях..Net 4.5
Просто используйте это:
Смотрите: TaskAwaiter , Task.Result , Task.RunSynchronously
.Net 4.0
Использовать этот:
...или это:
источник
.Result
может привести к тупику в определенных сценарияхResult
может легко вызвать тупик вasync
коде , как я опишу в своем блоге.Удивлен, никто не упомянул это:
Не так красиво, как некоторые другие методы здесь, но имеет следующие преимущества:
Wait
)AggregateException
(какResult
)Task
иTask<T>
( попробуйте сами! )Кроме того, поскольку
GetAwaiter
это типизированный утиный тип, это должно работать для любого объекта, возвращаемого из асинхронного метода (например,ConfiguredAwaitable
илиYieldAwaitable
), а не только для задач.edit: Пожалуйста, обратите внимание, что этот подход (или использование
.Result
) может привести к взаимоблокировке, если вы не добавите.ConfigureAwait(false)
каждый раз, когда ожидаете, все асинхронные методы, из которых можно получить доступBlahAsync()
(не только те, которые он вызывает напрямую). Объяснение .Если вам лень добавлять
.ConfigureAwait(false)
везде, и вы не заботитесь о производительности, вы можете сделать этоисточник
GetAwaiter()
: «Этот метод предназначен для пользователя компилятора, а не для непосредственного использования в коде».Намного проще выполнить задачу в пуле потоков, чем пытаться обмануть планировщик, чтобы запустить его синхронно. Таким образом, вы можете быть уверены, что это не тупик. Производительность зависит от переключения контекста.
источник
Task.Run(DoSomethingAsync)
вместо этого? Это удаляет один уровень делегатов.Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());
является более явным и решает вопрос @sgnsajgon о том, что он может возвращать Task <Task <MyResult >>. Правильная перегрузка Task.Run выбирается в любом случае, но асинхронный делегат делает ваше намерение очевидным.Лучший ответ - нет , детали зависят от ситуации.
Это свойство getter / setter? В большинстве случаев лучше иметь асинхронные методы, чем "асинхронные свойства". (Для получения дополнительной информации см. Мой пост в блоге об асинхронных свойствах ).
Это приложение MVVM, и вы хотите сделать асинхронное связывание данных? Затем используйте что-то вроде my
NotifyTask
, как описано в моей статье MSDN об асинхронном связывании данных .Это конструктор? Тогда вы, вероятно, захотите рассмотреть асинхронный фабричный метод. (Для получения дополнительной информации см. Мой пост в блоге об асинхронных конструкторах ).
Почти всегда лучший ответ, чем синхронизация по асинхронности.
Если это невозможно для вашей ситуации (и вы знаете это, задавая вопрос, описывающий ситуацию ), то я бы порекомендовал просто использовать синхронный код. Асинхронный полностью лучше; Синхронизация полностью на втором месте. Синхронизация по асинхронности не рекомендуется.
Однако в некоторых ситуациях синхронизация по асинхронности необходима. В частности, вы ограничены вызывающим кодом, так что вы должны быть синхронизированы (и у вас нет абсолютно никакого способа переосмыслить или реструктурировать ваш код, чтобы разрешить асинхронность), и вы должны вызвать асинхронный код. Это очень редкая ситуация, но время от времени она возникает.
В этом случае вам понадобится один из хаков, описанных в моей статье о разработке Brownfield
async
, а именно:GetAwaiter().GetResult()
). Обратите внимание, что это может вызвать взаимные блокировки (как я описал в моем блоге).Task.Run(..).GetAwaiter().GetResult()
). Обратите внимание, что это будет работать только в том случае, если асинхронный код может выполняться в потоке пула потоков (т. Е. Не зависит от контекста пользовательского интерфейса или ASP.NET).Вложенные циклы сообщений являются наиболее опасными из всех хаков, потому что это вызывает повторный вход . Повторный вход чрезвычайно сложен, и (IMO) является причиной большинства ошибок приложений в Windows. В частности, если вы находитесь в потоке пользовательского интерфейса и блокируете рабочую очередь (ожидая завершения асинхронной работы), то CLR фактически выполняет для вас некоторую перекачку сообщений - он фактически обрабатывает некоторые сообщения Win32 изнутри вашего код . О, и вы не представляете, какие сообщения - когда Крис Брамм говорит: «Разве не было бы замечательно точно знать, что будет накачано? К сожалению, накачка - это черное искусство, которое находится за пределами понимания смертных». тогда у нас действительно нет надежды узнать.
Итак, когда вы блокируете таким образом в потоке пользовательского интерфейса, вы напрашиваетесь на неприятности. Другая цитата cbrumme из той же статьи: «Время от времени клиенты внутри или за пределами компании обнаруживают, что мы накачиваем сообщения во время управляемой блокировки в STA [поток пользовательского интерфейса]. Это законное беспокойство, потому что они знают, что это очень сложно написать код, который является надежным перед лицом повторного входа ".
Да, это так. Очень сложно написать код, который является надежным перед лицом повторного появления. А вложенные циклы сообщений вынуждают вас писать код, надежный перед лицом повторного входа. Поэтому принятый (и наиболее upvoted) ответ на этот вопрос является чрезвычайно опасным на практике.
Если у вас полностью отсутствуют другие возможности - вы не можете изменить свой код, вы не можете реструктурировать его так, чтобы он был асинхронным - вас заставляет неизменный вызывающий код синхронизировать - вы не можете изменить нижестоящий код для синхронизации - вы не можете заблокировать - вы не можете запустить асинхронный код в отдельном потоке - тогда и только тогда вы должны рассмотреть возможность повторного входа.
Если вы окажетесь в этом углу, я бы порекомендовал использовать что-то вроде
Dispatcher.PushFrame
приложений WPF , цикл сApplication.DoEvents
приложениями WinForm, а для общего случая - мой собственныйAsyncContext.Run
.источник
Main()
методом не компилируется; в каком - то момент вы получили , чтобы преодолеть разрыв между синхронными и асинхронными мирами. Это не « очень редкая ситуация» , это необходимо буквально в каждой программе, которая вызывает асинхронный метод. Нет возможности не «делать синхронизацию по асинхронности» , просто есть возможность перекладывать эту нагрузку на вызывающий метод вместо того, чтобы брать его на плечи в тот, который вы сейчас пишете.async
все методы в моем приложении сейчас. И это много. Разве это не может быть просто по умолчанию?Если я правильно понимаю ваш вопрос - код, который хочет синхронный вызов асинхронного метода, выполняется в приостановленном потоке диспетчера. И вы хотите на самом деле синхронно блокировать этот поток, пока не завершится асинхронный метод.
Асинхронные методы в C # 5 основаны на эффективном разделении метода на части под капотом и возвращении a,
Task
который может отслеживать полное завершение всего шабанга. Однако то, как выполняются измельченные методы, может зависеть от типа выражения, передаваемогоawait
оператору.Большую часть времени вы будете использовать
await
выражение типаTask
. Реализацияawait
шаблона в Task является «умной» в том смысле, что она подчиняется томуSynchronizationContext
, что в основном приводит к следующему:await
находится в потоке цикла сообщений Dispatcher или WinForms, он гарантирует, что фрагменты асинхронного метода выполняются как часть обработки очереди сообщений.await
находится в потоке пула потоков, то оставшиеся фрагменты асинхронного метода появляются в любом месте пула потоков.Вот почему вы, вероятно, сталкиваетесь с проблемами - реализация асинхронного метода пытается запустить остальную часть в Dispatcher - даже если она приостановлена.
.... резервное копирование! ....
Я должен задать вопрос, почему вы пытаетесь синхронно заблокировать асинхронный метод? Это отрицательно скажется на том, почему метод должен вызываться асинхронно. В общем, когда вы начинаете использовать
await
метод Dispatcher или UI, вам нужно включить асинхронность всего потока UI. Например, если ваш стек вызовов был примерно таким:WebRequest.GetResponse()
YourCode.HelperMethod()
YourCode.AnotherMethod()
YourCode.EventHandlerMethod()
[UI Code].Plumbing()
-WPF
илиWinForms
кодWPF
илиWinForms
Message LoopЗатем, как только код был преобразован в асинхронный режим, вы, как правило, получите
WebRequest.GetResponseAsync()
YourCode.HelperMethodAsync()
YourCode.AnotherMethodAsync()
YourCode.EventHandlerMethodAsync()
[UI Code].Plumbing()
-WPF
илиWinForms
кодWPF
илиWinForms
Message LoopНа самом деле отвечая
Класс AsyncHelpers, приведенный выше, на самом деле работает, потому что он ведет себя как вложенный цикл обработки сообщений, но он устанавливает свою собственную параллельную механику для Dispatcher, а не пытается выполнить ее на самом Dispatcher. Это один из обходных путей для вашей проблемы.
Другой обходной путь - выполнить асинхронный метод в потоке потоков и затем дождаться его завершения. Сделать это легко - вы можете сделать это с помощью следующего фрагмента:
Конечным API будет Task.Run (...), но с CTP вам понадобятся суффиксы Ex ( объяснение здесь ).
источник
TaskEx.RunEx(GetCustomers).Result
приложение зависает, когда оно запускается в приостановленном потоке диспетчера. Кроме того, метод GetCustomers () обычно запускается асинхронно, однако в одной ситуации он должен выполняться синхронно, поэтому я искал способ сделать это без создания версии синхронизации метода.async
методы; Конечно, следует избегать вложенных циклов.Это хорошо работает для меня
источник
MyAsyncMethod().RunTaskSynchronously();
Я нашел самый простой способ запустить задачу синхронно и без блокировки потока пользовательского интерфейса - это использовать RunSynchronously (), например:
В моем случае у меня есть событие, которое срабатывает, когда что-то происходит. Я не знаю, сколько раз это произойдет. Итак, я использую приведенный выше код в своем событии, поэтому всякий раз, когда он запускается, он создает задачу. Задачи выполняются синхронно, и это прекрасно работает для меня. Я был просто удивлен, что мне понадобилось так много времени, чтобы понять это, учитывая, насколько это просто. Обычно рекомендации гораздо сложнее и подвержены ошибкам. Это было просто и чисто.
источник
Я сталкивался с этим несколько раз, в основном в модульном тестировании или в разработке службы Windows. В настоящее время я всегда использую эту функцию:
Это просто, легко, и у меня не было проблем.
источник
Я нашел этот код в компоненте Microsoft.AspNet.Identity.Core, и он работает.
источник
Просто небольшая заметка - этот подход:
работает на WinRT.
Позволь мне объяснить:
Более того, этот подход работает только для решений Магазина Windows!
Примечание. Этот способ не является потокобезопасным, если вы вызываете свой метод внутри другого асинхронного метода (согласно комментариям @Servy).
источник
CancellationToken
для своего решения.В вашем коде ваше первое ожидание выполнения задачи, но вы еще не запустили ее, поэтому она ждет бесконечно. Попробуй это:
Редактировать:
Вы говорите, что получаете исключение. Пожалуйста, опубликуйте более подробную информацию, включая трассировку стека
Mono содержит следующий контрольный пример:
Проверьте, работает ли это для вас. Если это не так, хотя очень маловероятно, у вас может быть странная сборка Async CTP. Если это работает, вы можете проверить, что именно генерирует компилятор и как
Task
создание экземпляров отличается от этого примера.Изменить № 2:
Я проверил с помощью Reflector, что описанное вами исключение возникает, когда оно
m_action
естьnull
. Это немного странно, но я не эксперт по Async CTP. Как я уже говорил, вы должны декомпилировать код и посмотреть , как именноTask
в настоящее время экземпляр любого , как придет егоm_action
ISnull
.PS Какое дело со случайными понижениями? Хотите разработать?
источник
RunSynchronously may not be called on a task unbound to a delegate
. Google не помогает, так как все результаты для этого на китайском ...await
ключевого слова. Исключение, опубликованное в моем предыдущем комментарии, является исключением, которое я получаю, хотя это одно из немногих, что я не могу найти в Google и найти причину или решение.async
иasync
ключевые слова - не более чем синтаксический сахар. Компилятор генерирует код для созданияTask<Customer>
вGetCustomers()
так что это , где я выглядел бы первым. Что касается исключения, вы отправили только сообщение об исключении, которое бесполезно без типа исключения и трассировки стека. ВызватьToString()
метод исключения и опубликовать вывод в вопросе.Протестировано в .Net 4.6. Это также может избежать тупика.
Для возврата асинхронного метода
Task
.Для возврата асинхронного метода
Task<T>
Редактировать :
Если вызывающий работает в потоке пула потоков (или вызывающий также находится в задании), он все равно может вызвать взаимоблокировку в некоторых ситуациях.
источник
Result
идеально подходит для работы, если вы хотите синхронный вызов, и в противном случае опасно. В имениResult
или в значении нет ничего,Result
что указывало бы на блокирующий вызов. Это действительно должно быть переименовано.используйте приведенный ниже фрагмент кода
источник
Почему бы не создать звонок как:
это не асинхронно.
источник
Этот ответ предназначен для всех, кто использует WPF для .NET 4.5.
Если вы попытаетесь выполнить его
Task.Run()
в потоке GUI, онtask.Wait()
будет зависать бесконечно, если у вас нетasync
ключевого слова в определении вашей функции.Этот метод расширения решает проблему, проверяя, находимся ли мы в потоке графического интерфейса и, если это так, запускаем задачу в потоке диспетчера WPF.
Этот класс может служить связующим звеном между миром async / await и миром non-async / await в ситуациях, когда это неизбежно, таких как свойства MVVM или зависимости от других API, которые не используют async / await.
источник
Просто звоните
.Result;
или.Wait()
риск тупиков, как многие говорили в комментариях. Поскольку большинство из нас любят вкладыши, вы можете использовать их для.Net 4.5<
Получение значения с помощью асинхронного метода:
Синхронно вызывая асинхронный метод
Из-за использования
Task.Run
.Источник:
https://stackoverflow.com/a/32429753/3850405
источник
Я думаю, что следующий вспомогательный метод также может решить проблему.
Может быть использован следующий способ:
источник
Это работает для меня
источник
Я обнаружил, что SpinWait работает довольно хорошо для этого.
Вышеупомянутый подход не должен использовать .Result или .Wait (). Это также позволяет вам указать тайм-аут, чтобы вы не застряли навсегда на случай, если задача никогда не завершится.
источник
На wp8:
Заверните:
Назови это:
источник
источник
Или вы можете просто пойти с:
Чтобы это скомпилировать, убедитесь, что вы ссылаетесь на сборку расширения:
источник
Попробуйте следующий код, он работает для меня:
источник