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

547

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

Асинхронный метод:

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: null

Источник: 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()
  • 40
    Лучший ответ на вопрос «Как я могу синхронно вызывать асинхронный метод» - «нет». Есть хаки, которые пытаются заставить его работать, но все они имеют очень тонкие ловушки. Вместо этого сделайте резервную копию и исправьте код, который вам необходим для этого.
  • 44
    @Stephen Cleary Абсолютно согласен, но иногда это просто неизбежно, например, когда ваш код зависит от какого-либо стороннего API, который не использует async / await. Кроме того, при привязке к свойствам WPF при использовании MVVM буквально невозможно использовать async / await, поскольку это не поддерживается в свойствах.
Показать ещё 14 комментариев
Теги:
async-await
asynchronous
c#-5.0

25 ответов

425
Лучший ответ

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

Его можно вызвать с помощью:

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

Код от здесь

public static class AsyncHelpers
{
    /// <summary>
    /// Execute 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 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;
        }
    }
}
  • 25
    Чтобы узнать, как это работает, Стивен Туб (Мистер Параллель) написал серию сообщений об этом. Часть 1 Часть 2 Часть 3
  • 14
    Я обновил код Джона для работы без переноса задач в lambdas: github.com/tejacques/AsyncBridge . По сути, вы работаете с асинхронными блоками с помощью оператора using. Все внутри блока использования происходит асинхронно, с ожиданием в конце. Недостатком является то, что вам нужно развернуть задачу самостоятельно в обратном вызове, но это все еще довольно элегантно, особенно если вам нужно вызывать несколько асинхронных функций одновременно.
Показать ещё 16 комментариев
286

Имейте в виду, что этому ответу три года. Я написал это, основываясь в основном на опыте с .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();
  • 0
    Если вы хотите использовать async в .NET 4.0, вы можете установить пакет NuGet async: nuget.org/packages/Microsoft.Bcl.Async.
  • 60
    .Result может привести к тупику в определенных сценариях.
Показать ещё 11 комментариев
100

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

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

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

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

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


edit: Обратите внимание, что для этого подхода (или с помощью .Result) .Result взаимоблокировка, если только вы не уверены, что добавляете .ConfigureAwait(false) каждый раз, когда ожидаете, для всех асинхронных методов, которые могут быть BlahAsync() из 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 ();
  • 1
    Это вызвало тупик внутри веб-метода asmx. Тем не менее, завершение вызова метода в Task.Run () заставило его работать: Task.Run (() => BlahAsync ()). GetAwaiter (). GetResult ()
Показать ещё 3 комментария
65

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

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; 
  • 0
    А если задача недействительна (без результата)?
  • 3
    Затем вы вызываете task.Wait (). Тип данных просто Задача.
Показать ещё 4 комментария
45

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

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

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

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

Там почти всегда есть лучший ответ, чем делать sync-over-async.

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

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

В этом случае вам нужно будет использовать один из хаков, описанный в моей статье, в brownfield async development, в частности:

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

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

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

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

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

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

  • 0
    Стивен, есть еще один очень похожий вопрос, на который ты тоже дал замечательный ответ. Как вы думаете, один из них можно закрыть как дубликат, или, возможно, запрос на слияние или сначала вызвать мету (поскольку каждый q имеет ~ 200K просмотров более 200 голосов)? Предложения?
  • 0
    @AlexeiLevenkov: Я не чувствую себя правильно, делая это по нескольким причинам: 1) Ответ на связанный вопрос довольно устарел. 2) Я написал целую статью на эту тему, которая, по моему мнению, является более полной, чем любая существующая документация. 3) Принятый ответ на этот вопрос чрезвычайно популярен. 4) Я категорически против этого принятого ответа. Таким образом, закрытие этого как дублирования этого было бы злоупотреблением властью; закрытие этого как дубликата этого (или слияния) даст еще более опасный ответ. Я оставлю это, и оставлю это сообществу.
Показать ещё 5 комментариев
21

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

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;
    }
}
  • 0
    Вы должны также использовать метод Task.Unwrap, потому что ваш Task.Wait заявление вызывает ожидание внешней задачи (созданный Task.Run), а не для внутреннего AWAIT т Задача передается в качестве параметра метода расширения. Ваш метод Task.Run возвращает не Task <T>, а Task <Task <T >>. В некоторых простых сценариях ваше решение может работает из - за TaskScheduler оптимизаций, например , с использованием метода TryExecuteTaskInline для выполнения задач в рамках текущего потока во время работы Wait Пожалуйста смотрите на мой комментарий к этому ответу.
  • 1
    Это не правильно. Task.Run вернет Task <T>. Смотрите эту перегрузку msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
Показать ещё 1 комментарий
21

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

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

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

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

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

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

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

  1. [Top] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing() - код WPF или WinForms
  6. [Цикл сообщений] - Цикл сообщений WPF или WinForms

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

  1. [Top] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing() - код WPF или WinForms
  6. [Цикл сообщений] - Цикл сообщений WPF или WinForms

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

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

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

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

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

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

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

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

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

  • 0
    Маленький и эффективный ..
  • 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");

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

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

Я нашел этот код в компоненте 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();
}
10

Небольшая заметка - этот подход:

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 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 Store!

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

  • 0
    Я объяснил это решение, проверьте раздел РЕДАКТИРОВАТЬ.
  • 2
    Это может очень легко привести к взаимоблокировке при вызове в асинхронных ситуациях.
Показать ещё 7 комментариев
9

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

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_action имеет значение null.


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

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

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

Service.GetCustomers();

который не является асинхронным.

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

используйте ниже код snip

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

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

Приобретение значения через метод async:

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

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

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

Никаких проблем с блокировкой не произойдет из-за использования Task.Run.

Источник:

https://stackoverflow.com/questions/17248680/await-works-but-calling-task-result-hangs-deadlocks

3

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

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

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

Этот класс может действовать как клей между миром 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();
        }
    }
}
1

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

Для метода aysnc верните Task.

Task DoSomeWork()

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

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

Task<T> GetSomeValue()

var result = Task.Run(() => GetSomeValue()).Result;
  • 0
    Мой ответ почти через 8 лет :) Второй пример - создаст тупик во всех запланированных контекстах, которые в основном используются (консольное приложение / .NET core / desktop app / ...). здесь у вас есть более общий обзор того, о чем я сейчас говорю: medium.com/rubrikkgroup/…
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
    Пожалуйста, объясните голосование
  • 2
    ... Мне все еще интересно, почему за этот ответ проголосовали?
Показать ещё 5 комментариев
0

Вы можете использовать CoRoutines. См. Caliburn.Micro. У меня есть пользовательская реализация здесь.

-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

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

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();
        }
    }
}
-3

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

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

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

System.Net.Http.Formatting
-4

В wp8:

Оберните его:

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

Назовите его:

GetCustomersSynchronously();
  • 3
    Нет, это не сработает, потому что задача не ожидает делегата от конструктора (его делегат, а не задача ..)
-5
    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;
        }
    }
-8

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

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);
    }
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню