Как мне запустить синхронный асинхронный метод Task <T>?


629

Я изучаю 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()

46
Лучший ответ на вопрос «Как я могу синхронно вызывать асинхронный метод» - «нет». Есть хаки, которые пытаются заставить его работать, но все они имеют очень тонкие ловушки. Вместо этого сделайте резервную копию и исправьте код, который делает вас «нужным» для этого.
Стивен Клири

58
@Stephen Cleary Абсолютно согласен, но иногда это просто неизбежно, например, когда ваш код зависит от какого-либо стороннего API, который не использует async / await. Кроме того, при привязке к свойствам WPF при использовании MVVM буквально невозможно использовать async / await, поскольку это не поддерживается в свойствах.
Contango

3
@StephenCleary Не всегда. Я строю DLL, которая будет импортирована в GeneXus . Он не поддерживает ключевые слова async / await, поэтому я должен использовать только синхронные методы.
Диней

5
@StephenCleary 1) GeneXus - это 3-х точечный инструмент, и у меня нет доступа к его исходному коду; 2) GeneXus даже не имеет реализации «функций», поэтому я не могу понять, как я мог бы реализовать «обратный вызов» с такими вещами. Конечно, это будет более сложный обходной путь, чем Taskсинхронное использование ; 3) Я интегрирую GeneXus с драйвером MongoDB C # , который предоставляет некоторые методы только асинхронно
Dinei

1
@ygoe: используйте асинхронную блокировку, например SemaphoreSlim.
Стивен Клири

Ответы:


457

Вот обходной путь, который я нашел, который работает для всех случаев (включая приостановленных диспетчеров). Это не мой код, и я все еще работаю над его полным пониманием, но он работает.

Это можно вызвать используя:

customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());

Код отсюда

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}

28
Чтобы узнать, как это работает, Стивен Туб (Мистер Параллель) написал серию сообщений об этом. Часть 1 Часть 2 Часть 3
Кэмерон МакФарланд

18
Я обновил код Джона для работы без переноса задач в lambdas: github.com/tejacques/AsyncBridge . По сути, вы работаете с асинхронными блоками с помощью оператора using. Все внутри блока использования происходит асинхронно с ожиданием в конце. Недостатком является то, что вам нужно развернуть задачу самостоятельно в обратном вызове, но это все еще довольно элегантно, особенно если вам нужно вызывать несколько асинхронных функций одновременно.
Том Жак

17
@StephenCleary Хотя я вообще согласен с вами , что код должен быть асинхронной весь путь вниз, иногда вы попадаете в недопустимой ситуацию , когда один имеет , чтобы заставить его как синхронный вызов. По сути, моя ситуация такова, что весь мой код доступа к данным работает асинхронно. Мне нужно было создать карту сайта на основе карты сайта, и сторонняя библиотека, которую я использовал, была MvcSitemap. Теперь, когда кто-то расширяет его через DynamicNodeProviderBaseбазовый класс, он не может объявить его как asyncметод. Либо мне пришлось заменить новую библиотеку, либо просто вызвать синхронную операцию.
justin.lovell

6
@ justin.lovell: Да, ограничения библиотеки могут заставить нас взламывать, по крайней мере, до обновления библиотеки. Похоже, MvcSitemap - одна из таких ситуаций, где требуется взлом (фильтры MVC и дочерние действия тоже); Я просто отговариваю людей от этого в целом, потому что подобные хаки используются слишком часто, когда они не нужны. В частности, с MVC некоторые API-интерфейсы ASP.NET/MVC предполагают, что они имеют AspNetSynchronizationContext, поэтому этот конкретный хак не будет работать, если вы вызываете эти API-интерфейсы.
Стивен Клири

5
Этот код не будет работать. Если он вызывается из потока пула, он может вызвать тупиковую блокировку потока. Вызывающий пользователь заблокирует ожидание завершения операции, что может никогда не произойти, если он исчерпал пул потоков. Видеть эту статью .
ZunTzu

318

Имейте в виду, что этому ответу три года. Я написал это, основываясь в основном на опыте с .Net 4.0, и очень мало на 4.5, особенно на async-await. Вообще говоря, это хорошее простое решение, но оно иногда ломает вещи. Пожалуйста, прочитайте обсуждение в комментариях.

.Net 4.5

Просто используйте это:

// For Task<T>: will block until the task is completed...
var result = task.Result; 

// For Task (not Task<T>): will block until the task is completed...
task2.RunSynchronously();

Смотрите: TaskAwaiter , Task.Result , Task.RunSynchronously


.Net 4.0

Использовать этот:

var x = (IAsyncResult)task;
task.Start();

x.AsyncWaitHandle.WaitOne();

...или это:

task.Start();
task.Wait();

67
.Resultможет привести к тупику в определенных сценариях
Джорди Ланген

122
Resultможет легко вызвать тупик в asyncкоде , как я опишу в своем блоге.
Стивен Клири

8
@ StephhenCleary Я прочитал твой пост и попробовал сам. Честно говоря, я думаю, что кто-то в Microsoft был действительно пьян ... Это та же проблема, что и в winforms и фоновых потоках ....
AK_

9
Вопрос касается Задачи, возвращаемой асинхронным методом. Задача такого типа может быть уже запущена, выполнена или отменена, поэтому использование метода Task.RunSynchronously может привести к возникновению исключения InvalidOperationException . См. Страницу MSDN: Task.RunSynchronously Метод . Кроме того, эта задача, вероятно, создается методами Task.Factory.StartNew или Task.Run (внутри асинхронного метода), поэтому пытаться запустить ее снова опасно. Некоторые условия гонки могут возникнуть во время выполнения. С другой стороны, Task.Wait и Task.Result могут привести к тупику.
sgnsajgon

4
Запуск Синхронно работал для меня ... Я не знаю, что я что-то упустил, но это кажется предпочтительнее ужасов отмеченного ответа - я просто искал способ отключить асинхронное тестирование кода, чтобы просто остановить пользовательский интерфейс от повешения
JonnyRaa

121

Удивлен, никто не упомянул это:

public Task<int> BlahAsync()
{
    // ...
}

int result = BlahAsync().GetAwaiter().GetResult();

Не так красиво, как некоторые другие методы здесь, но имеет следующие преимущества:

  • это не глотает исключения (как Wait)
  • это не обернет любые исключения, добавленные в AggregateException(как Result)
  • работает для обоих Taskи Task<T>( попробуйте сами! )

Кроме того, поскольку GetAwaiterэто типизированный утиный тип, это должно работать для любого объекта, возвращаемого из асинхронного метода (например, ConfiguredAwaitableили YieldAwaitable), а не только для задач.


edit: Пожалуйста, обратите внимание, что этот подход (или использование .Result) может привести к взаимоблокировке, если вы не добавите .ConfigureAwait(false)каждый раз, когда ожидаете, все асинхронные методы, из которых можно получить доступ BlahAsync()(не только те, которые он вызывает напрямую). Объяснение .

// In BlahAsync() body
await FooAsync(); // BAD!
await FooAsync().ConfigureAwait(false); // Good... but make sure FooAsync() and
                                        // all its descendants use ConfigureAwait(false)
                                        // too. Then you can be sure that
                                        // BlahAsync().GetAwaiter().GetResult()
                                        // won't deadlock.

Если вам лень добавлять .ConfigureAwait(false)везде, и вы не заботитесь о производительности, вы можете сделать это

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()

1
У меня работает для простых вещей. Кроме того, если метод возвращает IAsyncOperation, мне сначала нужно было преобразовать его в задачу: BlahAsync (). AsTask (). GetAwaiter (). GetResult ();
Ли МакФерсон

3
Это вызвало тупик внутри веб-метода asmx. Тем не менее, оборачивая вызов метода в Task.Run (), он заработал: Task.Run (() => BlahAsync ()). GetAwaiter (). GetResult ()
Аугусто Баррето

Мне нравится этот подход лучше всего синтаксически, потому что он не включает лямбды.
Dythim

25
Пожалуйста, НЕ редактируйте ответы других людей, чтобы вставить ссылку на свой собственный. Если вы считаете, что ваш ответ лучше, оставьте его вместо комментария.
Рэйчел

1
docs.microsoft.com/en-us/dotnet/api/… говорит о GetAwaiter(): «Этот метод предназначен для пользователя компилятора, а не для непосредственного использования в коде».
Феофил

75

Намного проще выполнить задачу в пуле потоков, чем пытаться обмануть планировщик, чтобы запустить его синхронно. Таким образом, вы можете быть уверены, что это не тупик. Производительность зависит от переключения контекста.

Task<MyResult> DoSomethingAsync() { ... }

// Starts the asynchronous task on a thread-pool thread.
// Returns a proxy to the original task.
Task<MyResult> task = Task.Run(() => DoSomethingAsync());

// Will block until the task is completed...
MyResult result = task.Result; 

3
Затем вы вызываете task.Wait (). Тип данных просто Задача.
Майкл Л Перри

1
Давайте предположим, что DoSomethingAsync () является длительным асинхронным методом в целом (внутренне он ожидает долгосрочную задачу), но он быстро возвращает управление потоком вызывающей стороне, таким образом, работа с аргументом lambda также быстро завершается. Результатом Tusk.Run () может быть Task <Task> или Task <Task <>> , поэтому вы ожидаете результата внешней задачи, которая выполняется быстро, но внутренней задачи (из-за ожидания длительного выполнения задания в асинхронном методе) все еще работает. Выводы заключаются в том, что нам, вероятно, нужно использовать подход Unwrap () (как это было сделано в посте @ J.Lennon) для достижения синхронного поведения асинхронного метода.
sgnsajgon

5
@sgnsajgon Вы не правы. Task.Run отличается от Task.Factory.StartNew тем, что он автоматически разворачивает результат. Смотрите эту статью .
ZunTzu

1
Могу ли я просто написать Task.Run(DoSomethingAsync)вместо этого? Это удаляет один уровень делегатов.
ygoe

1
Ага. Однако, идти в обратном направлении, как в, Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());является более явным и решает вопрос @sgnsajgon о том, что он может возвращать Task <Task <MyResult >>. Правильная перегрузка Task.Run выбирается в любом случае, но асинхронный делегат делает ваше намерение очевидным.
Майкл Л Перри

57

Я изучаю async / await и столкнулся с ситуацией, когда мне нужно синхронно вызывать асинхронный метод. Как я могу это сделать?

Лучший ответ - нет , детали зависят от ситуации.

Это свойство getter / setter? В большинстве случаев лучше иметь асинхронные методы, чем "асинхронные свойства". (Для получения дополнительной информации см. Мой пост в блоге об асинхронных свойствах ).

Это приложение MVVM, и вы хотите сделать асинхронное связывание данных? Затем используйте что-то вроде my NotifyTask, как описано в моей статье MSDN об асинхронном связывании данных .

Это конструктор? Тогда вы, вероятно, захотите рассмотреть асинхронный фабричный метод. (Для получения дополнительной информации см. Мой пост в блоге об асинхронных конструкторах ).

Почти всегда лучший ответ, чем синхронизация по асинхронности.

Если это невозможно для вашей ситуации (и вы знаете это, задавая вопрос, описывающий ситуацию ), то я бы порекомендовал просто использовать синхронный код. Асинхронный полностью лучше; Синхронизация полностью на втором месте. Синхронизация по асинхронности не рекомендуется.

Однако в некоторых ситуациях синхронизация по асинхронности необходима. В частности, вы ограничены вызывающим кодом, так что вы должны быть синхронизированы (и у вас нет абсолютно никакого способа переосмыслить или реструктурировать ваш код, чтобы разрешить асинхронность), и вы должны вызвать асинхронный код. Это очень редкая ситуация, но время от времени она возникает.

В этом случае вам понадобится один из хаков, описанных в моей статье о разработке Brownfieldasync , а именно:

  • Блокировка (например, GetAwaiter().GetResult()). Обратите внимание, что это может вызвать взаимные блокировки (как я описал в моем блоге).
  • Выполнение кода в потоке пула потоков (например, Task.Run(..).GetAwaiter().GetResult() ). Обратите внимание, что это будет работать только в том случае, если асинхронный код может выполняться в потоке пула потоков (т. Е. Не зависит от контекста пользовательского интерфейса или ASP.NET).
  • Вложенные циклы сообщений. Обратите внимание, что это будет работать только в том случае, если асинхронный код принимает только однопоточный контекст, а не конкретный тип контекста (большая часть кода пользовательского интерфейса и ASP.NET ожидает определенного контекста).

Вложенные циклы сообщений являются наиболее опасными из всех хаков, потому что это вызывает повторный вход . Повторный вход чрезвычайно сложен, и (IMO) является причиной большинства ошибок приложений в Windows. В частности, если вы находитесь в потоке пользовательского интерфейса и блокируете рабочую очередь (ожидая завершения асинхронной работы), то CLR фактически выполняет для вас некоторую перекачку сообщений - он фактически обрабатывает некоторые сообщения Win32 изнутри вашего код . О, и вы не представляете, какие сообщения - когда Крис Брамм говорит: «Разве не было бы замечательно точно знать, что будет накачано? К сожалению, накачка - это черное искусство, которое находится за пределами понимания смертных». тогда у нас действительно нет надежды узнать.

Итак, когда вы блокируете таким образом в потоке пользовательского интерфейса, вы напрашиваетесь на неприятности. Другая цитата cbrumme из той же статьи: «Время от времени клиенты внутри или за пределами компании обнаруживают, что мы накачиваем сообщения во время управляемой блокировки в STA [поток пользовательского интерфейса]. Это законное беспокойство, потому что они знают, что это очень сложно написать код, который является надежным перед лицом повторного входа ".

Да, это так. Очень сложно написать код, который является надежным перед лицом повторного появления. А вложенные циклы сообщений вынуждают вас писать код, надежный перед лицом повторного входа. Поэтому принятый (и наиболее upvoted) ответ на этот вопрос является чрезвычайно опасным на практике.

Если у вас полностью отсутствуют другие возможности - вы не можете изменить свой код, вы не можете реструктурировать его так, чтобы он был асинхронным - вас заставляет неизменный вызывающий код синхронизировать - вы не можете изменить нижестоящий код для синхронизации - вы не можете заблокировать - вы не можете запустить асинхронный код в отдельном потоке - тогда и только тогда вы должны рассмотреть возможность повторного входа.

Если вы окажетесь в этом углу, я бы порекомендовал использовать что-то вроде Dispatcher.PushFrameприложений WPF , цикл с Application.DoEventsприложениями WinForm, а для общего случая - мой собственный AsyncContext.Run.


Стивен, есть еще один очень похожий вопрос, на который ты тоже дал замечательный ответ. Как вы думаете, один из них можно закрыть как дубликат, или, возможно, запрос на слияние или сначала вызвать мету (поскольку каждый q имеет ~ 200K просмотров более 200 голосов)? Предложения?
Алексей Левенков

1
@AlexeiLevenkov: Я не чувствую, что поступаю так по нескольким причинам: 1) Ответ на связанный вопрос довольно устарел. 2) Я написал целую статью на эту тему, которая, по моему мнению, является более полной, чем любая существующая SO Q / A. 3) Принятый ответ на этот вопрос чрезвычайно популярен. 4) Я категорически против этого принятого ответа. Таким образом, закрытие этого как дублирования этого было бы злоупотреблением властью; закрытие этого как дублирования этого (или слияния) даст еще более опасный ответ. Я оставлю это и оставлю это сообществу.
Стивен Клири

Хорошо. Я подумаю о том, чтобы поднять это на мета, чем в каком-то смысле
Алексей Левенков

9
Этот ответ проходит долгий путь над моей головой. «Используйте асинхронный режим полностью» - это запутанный совет, потому что явно невозможно следовать. Программа с асинхронным Main()методом не компилируется; в каком - то момент вы получили , чтобы преодолеть разрыв между синхронными и асинхронными мирами. Это не « очень редкая ситуация» , это необходимо буквально в каждой программе, которая вызывает асинхронный метод. Нет возможности не «делать синхронизацию по асинхронности» , просто есть возможность перекладывать эту нагрузку на вызывающий метод вместо того, чтобы брать его на плечи в тот, который вы сейчас пишете.
Марк Амери

1
Отлично. Я собираюсь применить asyncвсе методы в моем приложении сейчас. И это много. Разве это не может быть просто по умолчанию?
ygoe

25

Если я правильно понимаю ваш вопрос - код, который хочет синхронный вызов асинхронного метода, выполняется в приостановленном потоке диспетчера. И вы хотите на самом деле синхронно блокировать этот поток, пока не завершится асинхронный метод.

Асинхронные методы в C # 5 основаны на эффективном разделении метода на части под капотом и возвращении a, Taskкоторый может отслеживать полное завершение всего шабанга. Однако то, как выполняются измельченные методы, может зависеть от типа выражения, передаваемогоawait оператору.

Большую часть времени вы будете использовать awaitвыражение типа Task. Реализация awaitшаблона в Task является «умной» в том смысле, что она подчиняется тому SynchronizationContext, что в основном приводит к следующему:

  1. Если входящий поток awaitнаходится в потоке цикла сообщений Dispatcher или WinForms, он гарантирует, что фрагменты асинхронного метода выполняются как часть обработки очереди сообщений.
  2. Если входящий поток awaitнаходится в потоке пула потоков, то оставшиеся фрагменты асинхронного метода появляются в любом месте пула потоков.

Вот почему вы, вероятно, сталкиваетесь с проблемами - реализация асинхронного метода пытается запустить остальную часть в Dispatcher - даже если она приостановлена.

.... резервное копирование! ....

Я должен задать вопрос, почему вы пытаетесь синхронно заблокировать асинхронный метод? Это отрицательно скажется на том, почему метод должен вызываться асинхронно. В общем, когда вы начинаете использовать awaitметод Dispatcher или UI, вам нужно включить асинхронность всего потока UI. Например, если ваш стек вызовов был примерно таким:

  1. [Верхний] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing()- WPFилиWinFormsкод
  6. [Message Loop] - WPFили WinFormsMessage Loop

Затем, как только код был преобразован в асинхронный режим, вы, как правило, получите

  1. [Верхний] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing()- WPFили WinFormsкод
  6. [Message Loop] - WPFили WinFormsMessage Loop

На самом деле отвечая

Класс AsyncHelpers, приведенный выше, на самом деле работает, потому что он ведет себя как вложенный цикл обработки сообщений, но он устанавливает свою собственную параллельную механику для Dispatcher, а не пытается выполнить ее на самом Dispatcher. Это один из обходных путей для вашей проблемы.

Другой обходной путь - выполнить асинхронный метод в потоке потоков и затем дождаться его завершения. Сделать это легко - вы можете сделать это с помощью следующего фрагмента:

var customerList = TaskEx.RunEx(GetCustomers).Result;

Конечным API будет Task.Run (...), но с CTP вам понадобятся суффиксы Ex ( объяснение здесь ).


+1 для подробного объяснения, однако TaskEx.RunEx(GetCustomers).Resultприложение зависает, когда оно запускается в приостановленном потоке диспетчера. Кроме того, метод GetCustomers () обычно запускается асинхронно, однако в одной ситуации он должен выполняться синхронно, поэтому я искал способ сделать это без создания версии синхронизации метода.
Рэйчел

+1 за "почему вы пытаетесь синхронно заблокировать асинхронный метод?" Всегда есть способ правильно использовать asyncметоды; Конечно, следует избегать вложенных циклов.
Стивен Клири

24

Это хорошо работает для меня

public static class TaskHelper
{
    public static void RunTaskSynchronously(this Task t)
    {
        var task = Task.Run(async () => await t);
        task.Wait();
    }

    public static T RunTaskSynchronously<T>(this Task<T> t)
    {
        T res = default(T);
        var task = Task.Run(async () => res = await t);
        task.Wait();
        return res;
    }
}

Вам также необходимо использовать метод Task.Unwrap , потому что ваш оператор Task.Wait вызывает ожидание внешней Задачи (созданной Task.Run ), а не для внутреннего ожидания Задача, переданная в качестве параметра метода расширения. Ваш метод Task.Run возвращает не Task <T>, а Task <Task <T >>. В некоторых простых сценариях ваше решение может работает из - за TaskScheduler оптимизаций, например , используя TryExecuteTaskInline метод для выполнения задач в рамках текущего потока во время Wait операции .Please взгляд на мой комментарий к этому ответу.
sgnsajgon

1
Это не правильно. Task.Run вернет Task <T>. Смотрите эту перегрузку msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
Клемент,

Как это должно быть использовано? Это тупики в WPF:MyAsyncMethod().RunTaskSynchronously();
ygoe

18

Я нашел самый простой способ запустить задачу синхронно и без блокировки потока пользовательского интерфейса - это использовать RunSynchronously (), например:

Task t = new Task(() => 
{ 
   //.... YOUR CODE ....
});
t.RunSynchronously();

В моем случае у меня есть событие, которое срабатывает, когда что-то происходит. Я не знаю, сколько раз это произойдет. Итак, я использую приведенный выше код в своем событии, поэтому всякий раз, когда он запускается, он создает задачу. Задачи выполняются синхронно, и это прекрасно работает для меня. Я был просто удивлен, что мне понадобилось так много времени, чтобы понять это, учитывая, насколько это просто. Обычно рекомендации гораздо сложнее и подвержены ошибкам. Это было просто и чисто.


1
Но как мы можем использовать этот метод, когда асинхронный код возвращает то, что нам нужно?
С.Серпушан

16

Я сталкивался с этим несколько раз, в основном в модульном тестировании или в разработке службы Windows. В настоящее время я всегда использую эту функцию:

        var runSync = Task.Factory.StartNew(new Func<Task>(async () =>
        {
            Trace.WriteLine("Task runSync Start");
            await TaskEx.Delay(2000); // Simulates a method that returns a task and
                                      // inside it is possible that there
                                      // async keywords or anothers tasks
            Trace.WriteLine("Task runSync Completed");
        })).Unwrap();
        Trace.WriteLine("Before runSync Wait");
        runSync.Wait();
        Trace.WriteLine("After runSync Waited");

Это просто, легко, и у меня не было проблем.


Это единственный, который не зашел в тупик для меня.
AndreFeijo

15

Я нашел этот код в компоненте Microsoft.AspNet.Identity.Core, и он работает.

private static readonly TaskFactory _myTaskFactory = new 
     TaskFactory(CancellationToken.None, TaskCreationOptions.None, 
     TaskContinuationOptions.None, TaskScheduler.Default);

// Microsoft.AspNet.Identity.AsyncHelper
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
    CultureInfo cultureUi = CultureInfo.CurrentUICulture;
    CultureInfo culture = CultureInfo.CurrentCulture;
    return AsyncHelper._myTaskFactory.StartNew<Task<TResult>>(delegate
    {
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = cultureUi;
        return func();
    }).Unwrap<TResult>().GetAwaiter().GetResult();
}


13

Просто небольшая заметка - этот подход:

Task<Customer> task = GetCustomers();
task.Wait()

работает на WinRT.

Позволь мне объяснить:

private void TestMethod()
{
    Task<Customer> task = GetCustomers(); // call async method as sync and get task as result
    task.Wait(); // wait executing the method
    var customer = task.Result; // get's result.
    Debug.WriteLine(customer.Name); //print customer name
}
public class Customer
{
    public Customer()
    {
        new ManualResetEvent(false).WaitOne(TimeSpan.FromSeconds(5));//wait 5 second (long term operation)
    }
    public string Name { get; set; }
}
private Task<Customer> GetCustomers()
{
    return Task.Run(() => new Customer
    {
        Name = "MyName"
    });
}

Более того, этот подход работает только для решений Магазина Windows!

Примечание. Этот способ не является потокобезопасным, если вы вызываете свой метод внутри другого асинхронного метода (согласно комментариям @Servy).


Я объяснил это решение, проверьте раздел РЕДАКТИРОВАТЬ.
RredCat

2
Это может очень легко привести к взаимоблокировке при вызове в асинхронных ситуациях.
Serv

@ Служение имеет смысл. Так что, как я понял, использование Wait (timeOut) может помочь, верно?
RredCat

1
Затем вам нужно позаботиться о том, чтобы истекло время ожидания, когда операция фактически не выполнена, что очень плохо, а также время, потраченное на ожидание времени ожидания в тех случаях, когда оно блокируется (и в этом случае вы все еще продолжаете когда это не сделано). Так что нет, это не решает проблему.
Serv

@Servy Похоже, я должен реализовать CancellationTokenдля своего решения.
RredCat

10

В вашем коде ваше первое ожидание выполнения задачи, но вы еще не запустили ее, поэтому она ждет бесконечно. Попробуй это:

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Редактировать:

Вы говорите, что получаете исключение. Пожалуйста, опубликуйте более подробную информацию, включая трассировку стека
Mono содержит следующий контрольный пример:

[Test]
public void ExecuteSynchronouslyTest ()
{
        var val = 0;
        Task t = new Task (() => { Thread.Sleep (100); val = 1; });
        t.RunSynchronously ();

        Assert.AreEqual (1, val);
}

Проверьте, работает ли это для вас. Если это не так, хотя очень маловероятно, у вас может быть странная сборка Async CTP. Если это работает, вы можете проверить, что именно генерирует компилятор и какTask создание экземпляров отличается от этого примера.

Изменить № 2:

Я проверил с помощью Reflector, что описанное вами исключение возникает, когда оно m_actionесть null. Это немного странно, но я не эксперт по Async CTP. Как я уже говорил, вы должны декомпилировать код и посмотреть , как именно Taskв настоящее время экземпляр любого , как придет его m_actionIS null.


PS Какое дело со случайными понижениями? Хотите разработать?


Я изменил свой вопрос, чтобы сделать код, который я пытался, немного более понятным. RunSynchronously возвращает ошибку RunSynchronously may not be called on a task unbound to a delegate. Google не помогает, так как все результаты для этого на китайском ...
Rachel

Я думаю, что разница в том, что я не создаю задание, а затем пытаюсь его запустить. Вместо этого задача создается асинхронным методом при использовании awaitключевого слова. Исключение, опубликованное в моем предыдущем комментарии, является исключением, которое я получаю, хотя это одно из немногих, что я не могу найти в Google и найти причину или решение.
Рэйчел

1
asyncи asyncключевые слова - не более чем синтаксический сахар. Компилятор генерирует код для создания Task<Customer>в GetCustomers()так что это , где я выглядел бы первым. Что касается исключения, вы отправили только сообщение об исключении, которое бесполезно без типа исключения и трассировки стека. Вызвать ToString()метод исключения и опубликовать вывод в вопросе.
Дан Абрамов

@gaearon: я опубликовал информацию об исключении и трассировку стека в моем первоначальном вопросе.
Рэйчел

2
@gaearon Я думаю, что вы получили отрицательные отзывы, потому что ваш пост не имеет отношения к вопросу. Речь идет об асинхронных методах ожидания, а не о простых методах, возвращающих задачи. Более того, на мой взгляд, механизм асинхронного ожидания является синтаксическим сахаром, но не настолько тривиальным - есть продолжение, захват контекста, возобновление локального контекста, улучшенная обработка локальных исключений и многое другое. Затем не следует вызывать метод RunSynchronously для результата асинхронного метода, поскольку по определению асинхронный метод должен возвращать задание, которое в данный момент запланировано как минимум, и более одного раза находится в состоянии выполнения.
sgnsajgon

9

Протестировано в .Net 4.6. Это также может избежать тупика.

Для возврата асинхронного метода Task.

Task DoSomeWork();
Task.Run(async () => await DoSomeWork()).Wait();

Для возврата асинхронного метода Task<T>

Task<T> GetSomeValue();
var result = Task.Run(() => GetSomeValue()).Result;

Редактировать :

Если вызывающий работает в потоке пула потоков (или вызывающий также находится в задании), он все равно может вызвать взаимоблокировку в некоторых ситуациях.


1
Мой ответ через почти 8 лет :) Второй пример - создаст взаимоблокировку во всех запланированных контекстах, которые в основном используются (консольное приложение / .NET core / desktop app / ...). здесь у вас есть больше обзор , что я говорю о теперь: medium.com/rubrikkgroup/...
W92

Resultидеально подходит для работы, если вы хотите синхронный вызов, и в противном случае опасно. В имени Resultили в значении нет ничего, Resultчто указывало бы на блокирующий вызов. Это действительно должно быть переименовано.
Зодман

5

используйте приведенный ниже фрагмент кода

Task.WaitAll(Task.Run(async () => await service.myAsyncMethod()));

4

Почему бы не создать звонок как:

Service.GetCustomers();

это не асинхронно.


4
Это будет то, что я делаю, если я не могу заставить это работать ... создать версию Sync в дополнение к версии Async
Rachel

3

Этот ответ предназначен для всех, кто использует WPF для .NET 4.5.

Если вы попытаетесь выполнить его Task.Run()в потоке GUI, он task.Wait()будет зависать бесконечно, если у вас нет asyncключевого слова в определении вашей функции.

Этот метод расширения решает проблему, проверяя, находимся ли мы в потоке графического интерфейса и, если это так, запускаем задачу в потоке диспетчера WPF.

Этот класс может служить связующим звеном между миром async / await и миром non-async / await в ситуациях, когда это неизбежно, таких как свойства MVVM или зависимости от других API, которые не используют async / await.

/// <summary>
///     Intent: runs an async/await task synchronously. Designed for use with WPF.
///     Normally, under WPF, if task.Wait() is executed on the GUI thread without async
///     in the function signature, it will hang with a threading deadlock, this class 
///     solves that problem.
/// </summary>
public static class TaskHelper
{
    public static void MyRunTaskSynchronously(this Task task)
    {
        if (MyIfWpfDispatcherThread)
        {
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E99213. Task did not run to completion.");
            }
        }
        else
        {
            task.Wait();
            if (task.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E33213. Task did not run to completion.");
            }
        }
    }

    public static T MyRunTaskSynchronously<T>(this Task<T> task)
    {       
        if (MyIfWpfDispatcherThread)
        {
            T res = default(T);
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { res = await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E89213. Task did not run to completion.");
            }
            return res;
        }
        else
        {
            T res = default(T);
            var result = Task.Run(async () => res = await task);
            result.Wait();
            if (result.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E12823. Task did not run to completion.");
            }
            return res;
        }
    }

    /// <summary>
    ///     If the task is running on the WPF dispatcher thread.
    /// </summary>
    public static bool MyIfWpfDispatcherThread
    {
        get
        {
            return Application.Current.Dispatcher.CheckAccess();
        }
    }
}

3

Просто звоните .Result;или.Wait() риск тупиков, как многие говорили в комментариях. Поскольку большинство из нас любят вкладыши, вы можете использовать их для.Net 4.5<

Получение значения с помощью асинхронного метода:

var result = Task.Run(() => asyncGetValue()).Result;

Синхронно вызывая асинхронный метод

Task.Run(() => asyncMethod()).Wait();

Из-за использования Task.Run .

Источник:

https://stackoverflow.com/a/32429753/3850405


1

Я думаю, что следующий вспомогательный метод также может решить проблему.

private TResult InvokeAsyncFuncSynchronously<TResult>(Func< Task<TResult>> func)
    {
        TResult result = default(TResult);
        var autoResetEvent = new AutoResetEvent(false);

        Task.Run(async () =>
        {
            try
            {
                result = await func();
            }
            catch (Exception exc)
            {
                mErrorLogger.LogError(exc.ToString());
            }
            finally
            {
                autoResetEvent.Set();
            }
        });
        autoResetEvent.WaitOne();

        return result;
    }

Может быть использован следующий способ:

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);

1
Пожалуйста, объясните голосование
donttellya

2
... Мне все еще интересно, почему за этот ответ проголосовали?
donttellya

Это не настоящее «синхронно». Вы создаете два потока и ждете первых результатов других.
TMT

и все в стороне, это очень плохая идея.
Дэн Кладовая

1
Я просто написал почти идентичный код (строка за строкой тоже самое), но вместо этого использовал SemaphoreSlim вместо события автоматического сброса. Жаль, что я видел это раньше. Я считаю, что этот подход предотвращает взаимные блокировки и поддерживает выполнение асинхронного кода таким же, как в истинных асинхронных сценариях. Не совсем уверен, почему это плохая идея. Кажется, намного чище, чем другие подходы, которые я видел выше.
tmrog

0

Это работает для меня

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    public static class AsyncHelper
    {
        private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

        public static void RunSync(Func<Task> func)
        {
            _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
        }

        public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        {
            return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
        }
    }

    class SomeClass
    {
        public async Task<object> LoginAsync(object loginInfo)
        {
            return await Task.FromResult(0);
        }
        public object Login(object loginInfo)
        {
            return AsyncHelper.RunSync(() => LoginAsync(loginInfo));
            //return this.LoginAsync(loginInfo).Result.Content;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var someClass = new SomeClass();

            Console.WriteLine(someClass.Login(1));
            Console.ReadLine();
        }
    }
}

-1

Я обнаружил, что SpinWait работает довольно хорошо для этого.

var task = Task.Run(()=>DoSomethingAsyncronous());

if(!SpinWait.SpinUntil(()=>task.IsComplete, TimeSpan.FromSeconds(30)))
{//Task didn't complete within 30 seconds, fail...
   return false;
}

return true;

Вышеупомянутый подход не должен использовать .Result или .Wait (). Это также позволяет вам указать тайм-аут, чтобы вы не застряли навсегда на случай, если задача никогда не завершится.


1
Понижение говорит о том, что кому-то не нравится этот метод. Есть ли кто-то, кто может прокомментировать обратную сторону этого?
Grax32

В отсутствие даунготера, говорящего ПОЧЕМУ было дано отрицательное голосование, может ли кто-нибудь его проголосовать? :-)
Кертис

1
Это опрос (вращение), делегат будет принимать поток из пула до 1000 раз в секунду. Он не может вернуть управление сразу после завершения задачи (до 10 + мс ошибки). Если задание завершено по таймауту, задание будет продолжено, что делает его практически бесполезным.
Синатр

На самом деле, я использую это повсеместно в моем коде, и когда условие выполняется, SpinWaitSpinUntil () немедленно завершается. Поэтому, в зависимости от того, что наступит раньше, «условие выполнено» или тайм-аут, задача завершается. Это не продолжает работать.
Кертис

-3

На wp8:

Заверните:

Task GetCustomersSynchronously()
{
    Task t = new Task(async () =>
    {
        myCustomers = await GetCustomers();
    }
    t.RunSynchronously();
}

Назови это:

GetCustomersSynchronously();

3
Нет, это не сработает, потому что задача не ожидает делегата от конструктора (его делегат, а не задача ..)
Рико Сутер

-4
    private int GetSync()
    {
        try
        {
            ManualResetEvent mre = new ManualResetEvent(false);
            int result = null;

            Parallel.Invoke(async () =>
            {
                result = await SomeCalcAsync(5+5);
                mre.Set();
            });

            mre.WaitOne();
            return result;
        }
        catch (Exception)
        {
            return null;
        }
    }

-5

Или вы можете просто пойти с:

customerList = Task.Run<List<Customer>>(() => { return GetCustomers(); }).Result;

Чтобы это скомпилировать, убедитесь, что вы ссылаетесь на сборку расширения:

System.Net.Http.Formatting

-9

Попробуйте следующий код, он работает для меня:

public async void TaskSearchOnTaskList (SearchModel searchModel)
{
    try
    {
        List<EventsTasksModel> taskSearchList = await Task.Run(
            () => MakeasyncSearchRequest(searchModel),
            cancelTaskSearchToken.Token);

        if (cancelTaskSearchToken.IsCancellationRequested
                || string.IsNullOrEmpty(rid_agendaview_search_eventsbox.Text))
        {
            return;
        }

        if (taskSearchList == null || taskSearchList[0].result == Constants.ZERO)
        {
            RunOnUiThread(() => {
                textViewNoMembers.Visibility = ViewStates.Visible;                  
                taskListView.Visibility = ViewStates.Gone;
            });

            taskSearchRecureList = null;

            return;
        }
        else
        {
            taskSearchRecureList = TaskFooterServiceLayer
                                       .GetRecurringEvent(taskSearchList);

            this.SetOnAdapter(taskSearchRecureList);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("ActivityTaskFooter -> TaskSearchOnTaskList:" + ex.Message);
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.