나는이 public async void Foo()
나는 동기 방법에서 호출하려는 방법을. 지금까지 MSDN 설명서에서 본 모든 것은 비동기 메서드를 통해 비동기 메서드를 호출하는 것이지만 내 전체 프로그램은 비동기 메서드로 작성되지 않았습니다.
이것도 가능합니까?
다음은 비동기 메소드에서 이러한 메소드를 호출하는 예입니다. http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx
이제 동기화 메소드에서 이러한 비동기 메소드를 호출하려고합니다.
답변
비동기식 프로그래밍은 코드베이스를 통해 “성장”합니다. 좀비 바이러스와 비교 되었습니다 . 가장 좋은 해결책은 그것이 자라도록하는 것이지만 때로는 불가능합니다.
Nito.AsyncEx 라이브러리에 부분적으로 비동기 코드 기반을 처리하기 위해 몇 가지 유형을 작성했습니다 . 그러나 모든 상황에서 작동하는 솔루션은 없습니다.
해결책 A
컨텍스트와 다시 동기화 할 필요가없는 간단한 비동기 메소드가있는 경우 다음을 사용할 수 있습니다 Task.WaitAndUnwrapException
.
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
당신은 할 수 없습니다 사용하려는 Task.Wait
또는 Task.Result
그들이에 예외를 포장하기 때문에 AggregateException
.
이 솔루션은 MyAsyncMethod
컨텍스트와 다시 동기화되지 않는 경우에만 적합합니다 . 다시 말해서 모든 await
로 MyAsyncMethod
끝나야합니다 ConfigureAwait(false)
. 즉, UI 요소를 업데이트하거나 ASP.NET 요청 컨텍스트에 액세스 할 수 없습니다.
솔루션 B
MyAsyncMethod
컨텍스트와 다시 동기화해야하는 경우 AsyncContext.RunTask
중첩 된 컨텍스트를 제공하는 데 사용할 수 있습니다 .
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* 2014 년 4 월 14 일 업데이트 : 최신 버전의 라이브러리에서 API는 다음과 같습니다.
var result = AsyncContext.Run(MyAsyncMethod);
( 예외 를 전파하므로이 Task.Result
예제에서 사용 하는 것이 좋습니다 ).RunTask
Task
AsyncContext.RunTask
대신에 필요한 이유 Task.WaitAndUnwrapException
는 WinForms / WPF / SL / ASP.NET에서 발생할 수있는 약간의 교착 상태 가능성 때문입니다.
- 동기 메서드는 async 메서드를 호출하여를 얻습니다
Task
. - 동기 메소드는에서 블로킹 대기를 수행합니다
Task
. - 이
async
방법은await
없이 사용합니다ConfigureAwait
. - 는
Task
때만 완료하기 때문에이 상황에서 완료 할 수 없습니다async
방법은 완성입니다 에async
대한 연속을 예약하려고 시도하기 때문에이 메서드를 완료 할 수 없으며SynchronizationContext
동기식 메서드가 해당 컨텍스트에서 이미 실행 중이므로 WinForms / WPF / SL / ASP.NET에서 연속을 실행할 수 없습니다.
이것이 ConfigureAwait(false)
모든 async
방법 내에서 가능한 한 많이 사용하는 것이 좋은 이유 입니다.
솔루션 C
AsyncContext.RunTask
모든 시나리오에서 작동하지는 않습니다. 예를 들어, async
메소드가 UI 이벤트를 완료해야하는 무언가를 기다리는 경우 중첩 된 컨텍스트에서도 교착 상태가 발생합니다. 이 경우 async
스레드 풀 에서 메소드를 시작할 수 있습니다.
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
그러나이 솔루션에는 MyAsyncMethod
스레드 풀 컨텍스트에서 작동 하는 솔루션이 필요합니다 . 따라서 UI 요소를 업데이트하거나 ASP.NET 요청 컨텍스트에 액세스 할 수 없습니다. 이 경우 ConfigureAwait(false)
해당 await
설명을 추가 하고 솔루션 A를 사용할 수 있습니다.
2019 년 5 월 1 일 업데이트 : 최신 “최악의 사례”는 MSDN 기사 here에 있습니다 .
답변
마침내 내 문제를 해결 한 솔루션을 추가하면 누군가의 시간을 절약 할 수 있기를 바랍니다.
먼저 Stephen Cleary 의 두 기사를 읽으십시오 .
“비동기 코드에서 차단하지 말 것”의 “두 가지 모범 사례”에서 첫 번째 방법은 작동하지 않았고 두 번째 방법은 적용 할 수 없었습니다 (기본적으로 사용할 수있는 경우 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;
}
}
답변
Microsoft는 Async를 Sync로 실행하기 위해 AsyncHelper (내부) 클래스를 구축했습니다. 소스는 다음과 같습니다.
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));
}
라이센스 코드 조항에 관심이있는 사용자를 위해 다음은 Microsoft에서 MIT 라이센스를 받았음을 나타내는 주석이있는 매우 유사한 코드에 대한 링크입니다 (스레드에서 문화권 지원 만 추가). https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
답변
async Main은 이제 C # 7.2의 일부이며 프로젝트 고급 빌드 설정에서 활성화 할 수 있습니다.
C # <7.2의 경우 올바른 방법은 다음과 같습니다.
static void Main(string[] args)
{
MainAsync().GetAwaiter().GetResult();
}
static async Task MainAsync()
{
/*await stuff here*/
}
예를 들어
https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- 주제-구독
답변
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’키워드를 “이 장기 실행 태스크를 시작한 다음 제어를 호출 메소드로 리턴”으로 읽습니다. 장기 실행 작업이 완료되면 그 후 코드를 실행합니다. 대기 후 코드는 콜백 메소드였던 코드와 유사합니다. 논리적 흐름이라는 큰 차이점은 중단되지 않으므로 쓰기와 읽기가 훨씬 쉽습니다.
답변
100 % 확신 할 수는 없지만 이 블로그에 설명 된 기술 은 많은 상황에서 작동해야 한다고 생각합니다 .
따라서이
task.GetAwaiter().GetResult()
전파 로직을 직접 호출하려는 경우 사용할 수 있습니다 .
답변
그러나 임시 메시지 펌프 (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(...));
비동기 펌프에 대한 자세한 설명은 여기를 참조하십시오 .