Как вызвать асинхронный метод из синхронного метода в C #?

865

У меня есть public async void Foo()метод, который я хочу вызвать из синхронного метода. До сих пор все, что я видел из документации MSDN, это вызов асинхронных методов через асинхронные методы, но вся моя программа не построена с асинхронными методами.

Это вообще возможно?

Вот один пример вызова этих методов из асинхронного метода: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

Теперь я смотрю на вызов этих асинхронных методов из методов синхронизации.

башня
источник
2
Я столкнулся с этим также. Переопределяя RoleProvider, вы не можете изменить сигнатуру метода GetRolesForUser, поэтому вы не можете сделать метод асинхронным и поэтому не можете использовать await для вызова api асинхронно. Мое временное решение состояло в том, чтобы добавить синхронные методы в мой общий класс HttpClient, но я хотел бы знать, возможно ли это (и каковы могут быть последствия).
Тимоти Ли Рассел
1
Поскольку ваш async void Foo()метод не возвращает a, Taskэто означает, что вызывающая сторона не может знать, когда он завершится, Taskвместо этого он должен вернуть .
Дай
1
Связывание связанных вопросов о том, как это сделать в потоке пользовательского интерфейса.
нос

Ответы:

714

Асинхронное программирование «растет» через базу кода. Это было по сравнению с вирусом зомби . Лучшее решение - позволить ему расти, но иногда это невозможно.

Я написал несколько типов в моей библиотеке Nito.AsyncEx для работы с частично асинхронной базой кода. Там нет решения, которое работает в любой ситуации, хотя.

Решение А

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

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

Вы не хотите использовать Task.Waitили Task.Resultпотому что они обертывают исключения в AggregateException.

Это решение подходит только в том случае, если MyAsyncMethodне выполняется обратная синхронизация с его контекстом. Другими словами, каждый awaitв MyAsyncMethodдолжен заканчиваться ConfigureAwait(false). Это означает, что он не может обновлять какие-либо элементы пользовательского интерфейса или обращаться к контексту запроса ASP.NET.

Решение Б

Если MyAsyncMethodнеобходимо синхронизировать обратно с его контекстом, то вы можете использовать его AsyncContext.RunTaskдля предоставления вложенного контекста:

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* Обновление от 14.04.2014: В более поздних версиях библиотеки API выглядит следующим образом:

var result = AsyncContext.Run(MyAsyncMethod);

(Можно использовать Task.Resultв этом примере, потому что RunTaskбудут распространяться Taskисключения).

Причина, по которой вам может понадобиться AsyncContext.RunTaskвместо этого, Task.WaitAndUnwrapExceptionзаключается в том, что в WinForms / WPF / SL / ASP.NET возможна довольно слабая тупиковая ситуация:

  1. Синхронный метод вызывает асинхронный метод, получая Task.
  2. Синхронный метод блокирует ожидание Task.
  3. asyncМетод использует awaitбез ConfigureAwait.
  4. TaskНе может завершить в этой ситуации , потому что завершается только тогда , когда asyncметод законченный; asyncметод не может завершить , потому что он пытается планировать свое продолжение к SynchronizationContextи WinForms / WPF / SL / ASP.NET не позволит запустить продолжение , так как синхронный метод уже работает в этом контексте.

Это одна из причин, почему стоит использовать как можно больше ConfigureAwait(false)внутри каждого asyncметода.

Решение С

AsyncContext.RunTaskне будет работать в каждом сценарии. Например, если asyncметод ожидает чего-то, что требует события пользовательского интерфейса для завершения, то вы зашли в тупик даже с вложенным контекстом. В этом случае вы можете запустить asyncметод в пуле потоков:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

Однако это решение требует, MyAsyncMethodчтобы он работал в контексте пула потоков. Поэтому он не может обновлять элементы пользовательского интерфейса или обращаться к контексту запроса ASP.NET. И в этом случае вы можете также добавить ConfigureAwait(false)к его awaitзаявлениям и использовать решение А.

Обновление, 2019-05-01: Текущие «наихудшие практики» описаны в статье MSDN здесь .

Стивен Клири
источник
9
Решение A похоже на то, что я хочу, но похоже на task.WaitAndUnwrapException () не попало в .Net 4.5 RC; у него есть только задача. Wait (). Есть идеи, как это сделать с новой версией? Или это пользовательский метод расширения, который вы написали?
deadlydog
3
WaitAndUnwrapExceptionмой собственный метод из моей библиотеки AsyncEx . Официальные библиотеки .NET не предоставляют большой помощи для смешивания синхронизации и асинхронного кода (и вообще, вы не должны этого делать!). Я жду .NET 4.5 RTW и новый ноутбук без XP, прежде чем обновлять AsyncEx для работы на 4.5 (в настоящее время я не могу разрабатывать для 4.5, потому что я застрял на XP еще несколько недель).
Стивен Клири
12
AsyncContextтеперь есть Runметод, который принимает лямбда-выражения, поэтому вы должны использоватьvar result = AsyncContext.Run(() => MyAsyncMethod());
Стивен Клири
1
Я получил вашу библиотеку от Nuget, но, похоже, у нее нет RunTaskметода. Самая близкая вещь, которую я мог найти, была Run, но у этого нет Resultсобственности.
Асад Саидуддин
3
@Asad: Да, более чем через 2 года API изменился. Теперь вы можете просто сказатьvar result = AsyncContext.Run(MyAsyncMethod);
Стивен Клири
313

Добавление решения, которое окончательно решило мою проблему, надеюсь, сэкономит чье-то время.

Сначала прочитайте пару статей Стивена Клири :

Из «двух лучших практик» в «Не блокировать в асинхронном коде», первая не работала для меня, а вторая неприменима (в основном, если я могу использовать await, я делаю!).

Итак, вот мой обходной путь: оберните вызов внутри Task.Run<>(async () => await FunctionAsync());и, надеюсь, больше не тупик .

Вот мой код:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}
Tohid
источник
5
Два года спустя мне любопытно узнать, как продвигается это решение. Любые новости? Есть ли тонкость в этом подходе, который теряется на новичках?
Дэн Эспарза
26
Это не будет взаимоблокировкой, правда, но просто потому, что он вынужден работать в новом потоке, вне контекста синхронизации исходного потока. Тем не менее, есть определенные среды, в которых это очень плохо, особенно веб-приложения. Это может эффективно сократить вдвое доступные потоки для веб-сервера (один поток для запроса и один для этого). Чем больше вы это делаете, тем хуже становится. Вы можете потенциально заблокировать весь ваш веб-сервер.
Крис Пратт
30
@ChrisPratt - Вы можете быть правы, потому что Task.Run()это не лучшая практика в асинхронном коде. Но, опять же, каков ответ на оригинальный вопрос? Никогда не вызывать асинхронный метод синхронно? Мы желаем, но в реальном мире иногда приходится.
Tohid
1
@ Ты можешь попробовать библиотеку Стивена Клири. Я видел, как люди предполагают это, и Parallel.ForEachзлоупотребления не будут иметь эффекта в «реальном мире», и в конечном итоге это сломало серверы. Этот код подходит для консольных приложений, но, как говорит @ChrisPratt, его не следует использовать в веб-приложениях. Это может работать "сейчас", но не масштабируется.
Махдуми
1
Я заинтригован, чтобы начать создавать новые аккаунты в SO, отвечая на вопросы, просто чтобы набрать достаточно очков, чтобы проголосовать за этот ...
Giannis Paraskevopoulos
206

Microsoft создала AsyncHelper (внутренний) класс для запуска Async как Sync. Источник выглядит так:

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

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

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

Базовые классы Microsoft.AspNet.Identity имеют только методы Async, и для вызова их как Sync существуют классы с методами расширения, которые выглядят следующим образом (пример использования):

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

Для тех, кто обеспокоен условиями лицензирования кода, здесь есть ссылка на очень похожий код (просто добавлена ​​поддержка культуры в потоке), в которой есть комментарии, указывающие, что это лицензия MIT от Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

Эрик Филипс
источник
2
Мои асинхронные методы ждут других асинхронных методов. Я НЕ украшаю свои awaitзвонки ConfigureAwait(false). Я попытался использовать AsyncHelper.RunSyncдля вызова асинхронной функции из Application_Start()функции в Global.asax, и это похоже на работу. Означает ли это, что AsyncHelper.RunSyncнадежно не подвержена проблеме взаимоблокировки «маршала обратно в контекст звонящего», о которой я читал в других разделах этой публикации?
Bob.at.Indigo.Health
1
@ Bob.at.SBS зависит от того, что делает ваш код. Это не так просто, как если бы я использовал этот код, я в безопасности . Это очень минимальный и полубезопасный способ синхронного запуска асинхронных команд, его можно использовать ненадлежащим образом, чтобы вызвать взаимные блокировки.
Эрик Филипс
1
Спасибо. 2 дополнительных вопроса: 1) Можете ли вы привести пример того, чего асинхронный метод хочет избежать, что может привести к взаимоблокировке, и 2) являются ли взаимоблокировки в этом контексте часто зависящими от времени? Если это работает на практике, могу ли я по-прежнему зависать в моем коде в зависимости от времени?
Bob.at.Indigo.Health
@ Bob.at.SBS Я бы порекомендовал задать вопрос, используя кнопку Задать вопрос в правом верхнем углу. Вы можете включить ссылку на этот вопрос или ответ в свой вопрос в качестве ссылки.
Эрик Филипс
1
@ Bob.at ... код, предоставленный Эриком, прекрасно работает под Asp. net mvc5 и EF6, но не тогда, когда я попробовал какое-либо из других решений (ConfigureAwait (false) .GetAwaiter (). GetResult () или .result), которое полностью зависает в моем веб-приложении
LeonardoX
151

async Main теперь является частью C # 7.2 и может быть включен в расширенных настройках проекта.

Для C # <7.2 правильный путь:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

Вы увидите, что это используется во многих документах Microsoft, например: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- Тем-подписка

Ли Смит
источник
11
Понятия не имею, ПОЧЕМУ кто-то проголосовал за это. Это отлично сработало для меня. Без этого исправления мне пришлось бы распространять АСЫХ ВЕЗДЕ.
Заключенный НОЛЬ
11
Почему это лучше чем MainAsync().Wait()?
раздавить
8
Согласен. Вам просто нужно MainAsync (). Wait () вместо всего этого.
Хаджат
8
@crush Я описывал, как это может избежать некоторых тупиков. В некоторых ситуациях вызов .Wait () из пользовательского интерфейса или потока asp.net вызывает тупик. асинхронные тупики
Дэвид
6
@ClintB: Вы не должны делать это в ASP.NET Core. Веб-приложения особенно уязвимы для того, чтобы быть истощенными потоками, и каждый раз, когда вы делаете это, вы извлекаете поток из пула, который в противном случае использовался бы для обслуживания запроса. Это менее проблематично для настольных / мобильных приложений, поскольку они традиционно являются однопользовательскими.
Крис Пратт
52
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

Вы читаете ключевое слово «await» как «запустите эту долгосрочную задачу, а затем верните управление вызывающему методу». Как только долгосрочное задание выполнено, оно выполняет код после него. Код после await похож на то, что раньше было методами CallBack. Большая разница в том, что логический поток не прерывается, что делает его намного легче писать и читать.

Деспертар
источник
15
WaitОборачивает исключения и имеет возможность тупика.
Стивен Клири
Я думал, что если вы вызываете асинхронный метод без использования await, он будет выполняться синхронно. По крайней мере, это работает для меня (без звонка myTask.Wait). На самом деле, я получил исключение, когда попытался позвонить, myTask.RunSynchronously()потому что он уже был выполнен!
Авр
2
Мне нравится этот ответ. Хорошие комментарии для редактирования, маленькие и элегантные. Спасибо за помощь! Я все еще учусь параллелизму, поэтому все помогает :)
kayleeFrye_onDeck
2
Должен ли этот ответ работать до сих пор? Я только что попробовал это в проекте MVC Razor, и приложение просто зависает при доступе .Result.
Ушел кодирование
8
@TrueBlueAussie Это тупик контекста синхронизации. Ваш асинхронный код возвращается к контексту синхронизации, но Resultв тот момент вызов блокировался , поэтому он никогда не попадал туда. И Resultникогда не кончается, потому что он ждет того, кто ждет Resultконца, в основном: D
Luaan
40

Я не уверен на 100%, но я считаю, что методика, описанная в этом блоге, должна работать во многих случаях:

Таким образом, вы можете использовать, task.GetAwaiter().GetResult()если хотите напрямую вызывать эту логику распространения.

NStuke
источник
6
Решение A в ответе Стивена Клири выше использует этот метод. См. Источник WaitAndUnwrapException .
Орад
вам нужно использовать GetResult (), если функция, которую вы вызываете, void или task? Я имею в виду, если вы не хотите получать какие-либо результаты обратно
batmaci
Да, иначе он не будет блокироваться до завершения задачи. В качестве альтернативы вместо вызова GetAwaiter (). GetResult () вы можете вызвать .Wait ()
NStuke
1
Это часть "многих обстоятельств". Это зависит от общей модели потоков и от того, что другие потоки делают, чтобы определить, существует ли риск взаимоблокировки или нет.
NStuke
GetAwaiter (). GetResult () все еще может вызывать взаимоблокировки. Это только разворачивает исключение в более разумное.
nawfal
25

Однако есть хорошее решение, которое работает (почти: см. Комментарии) в любой ситуации: специальный механизм обработки сообщений (SynchronizationContext).

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

Код специального помощника по отправке сообщений:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

Применение:

AsyncPump.Run(() => FooAsync(...));

Более подробное описание асинхронного насоса доступно здесь .

Роберт Дж
источник
Контекст исключения и AsyncPump stackoverflow.com/questions/23161693/…
PreguntonCojoneroCabrón
Это не работает в сценарии Asp.net, так как вы можете случайно потерять HttpContext.Current.
Джош Моуч
12

Всем, кто больше обращает внимание на этот вопрос ...

Если вы посмотрите, Microsoft.VisualStudio.Services.WebApiесть класс с именем TaskExtensions. В этом классе вы увидите статический метод расширенияTask.SyncResult() , который, как и полностью, просто блокирует поток до тех пор, пока задача не вернется.

Внутренне он вызывает task.GetAwaiter().GetResult()довольно просто, но перегружен для работы с любым asyncвозвращаемым методом Task, Task<T>илиTask<HttpResponseMessage> ... синтаксический сахар, ребенок ... папа получил сладкий зуб.

Похоже, ...GetAwaiter().GetResult()это официальный MS-способ выполнения асинхронного кода в контексте блокировки. Кажется, работает очень хорошо для моего случая использования.

jrypkahauer
источник
3
Вы были со мной "как просто блоки".
Дауд ибн Карим
9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

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

var result=result.GetAwaiter().GetResult().AccessToken
Раджеш А
источник
6

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

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

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

base2
источник
5
Тогда это не будет вызывать метод синхронно, не так ли?
Джефф Меркадо
2
Как я понимаю, вопрос был в том, можно ли вызвать асинхронный метод из неасинхронного метода. Это не подразумевает необходимость вызова асинхронного метода блокирующим образом.
base2
Извините, ваши «они тоже должны быть отмечены async» отвлекли мое внимание от того, что вы на самом деле говорили.
Джефф Меркадо
Если меня не волнует асинхронность, можно ли так ее называть (и как насчет возможности взаимоблокировок в обернутых исключениях, из-за которых Стивен Клири все время ворчит?) У меня есть несколько тестовых методов (которые должны выполняться синхронно) это проверяет асинхронные методы. Я должен дождаться результата, прежде чем продолжить, чтобы я мог проверить результат асинхронного метода.
благоговение
6

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

Я нашел следующий кусок кода от Райана

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

тогда вы можете назвать это так

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
Wahid Bitar
источник
6
Это похоже на приведенный выше ответ : я что-то
упустил
2

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

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}
Иржи Эрник
источник
1
Работает с return task.GetAwaiter (). GetResult ();
Per G
да, но как насчет оригинального исключения?
Йиржи Эрник
.Результат, на мой взгляд, тот же, что и .GetAwaiter (). GetResult ()
за G
-2

Он может быть вызван из нового потока (НЕ из пула потоков!):

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );
Гарм
источник
-3

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

Например, в приложении Windows Phone 8 Silverlight вы можете сделать следующее:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

Надеюсь это поможет!

лисий
источник
-4

Если вы хотите запустить его Sync

MethodAsync().RunSynchronously()
SMJ
источник
3
Этот метод предназначен для запуска холодных задач. Обычно асинхронные методы возвращают горячую задачу, другими словами, задачу, которая уже запущена. вызов RunSynchronously()горячей задачи приводит к InvalidOperationException. Попробуйте это с этим кодом:Task.Run(() => {}).RunSynchronously();
Теодор Zoulias
-5
   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;
Арвинд Кумар Чаодхари
источник
2
Создать тупик. Лучше удали ответ.
PreguntonCojoneroCabrón
Task.Run (() => SaveAssetDataAsDraft ()). Result; - не создает тупик
Anubis