同期メソッドから呼び出したいpublic async void Foo()
メソッドがあります。これまでのところ、MSDNの資料から見たことは、非同期メソッドを介して非同期メソッドを呼び出すことだけでしたが、私のプログラム全体は非同期メソッドを使用して構築されていません。
これでも可能ですか?
非同期メソッドからこれらのメソッドを呼び出す1つの例を次に示します。 http://msdn.Microsoft.com/ja-jp/library/hh300224(v=vs.110).aspx
今私は、同期メソッドからこれらの非同期メソッドを呼び出すことを検討しています。
非同期プログラミングはコードベースを通じて「成長」します。 ゾンビウイルスと比較して 。最善の解決策はそれを成長させることですが、時にはそれが不可能な場合もあります。
私は部分的に非同期のコードベースを扱うために私の Nito.AsyncEx ライブラリにいくつかの型を書きました。しかし、あらゆる状況で機能する解決策はありません。
解決策A
コンテキストに同期し直す必要がない単純な非同期メソッドがある場合は、Task.WaitAndUnwrapException
を使用できます。
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
notは、AggregateException
で例外をラップするため、Task.Wait
またはTask.Result
を使用します。
この解決法は、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);
(この例では、RunTask
がTask
例外を伝搬するため、Task.Result
を使用しても構いません)。
あなたがAsyncContext.RunTask
の代わりにTask.WaitAndUnwrapException
を必要とするかもしれない理由はWinForms/WPF/SL/ASP.NETで起こるかなり微妙なデッドロックの可能性のためです:
Task
を取得します。Task
上でブロッキング待ちをします。async
メソッドはawait
なしでConfigureAwait
を使用します。Task
はasync
メソッドが終了したときにのみ完了するため、この状況では完了できません。 async
メソッドはSynchronizationContext
への継続をスケジュールしようとしているため完了できません。また、WinForms/WPF/SL/ASP.NETは、同期メソッドが既にそのコンテキストで実行されているため継続を実行できません。これが、すべてのasync
メソッド内で可能な限りConfigureAwait(false)
を使用することをお勧めする理由の1つです。
解決策C
AsyncContext.RunTask
はすべてのシナリオで機能するわけではありません。たとえば、async
メソッドが、UIイベントの完了を必要とするものを待っている場合は、ネストされたコンテキストでもデッドロックになります。その場合、スレッドプールでasync
メソッドを起動できます。
var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
ただし、このソリューションではスレッドプールのコンテキストで機能するMyAsyncMethod
が必要です。そのため、UI要素を更新したり、ASP.NETの要求コンテキストにアクセスしたりすることはできません。その場合は、await
ステートメントにConfigureAwait(false)
を追加して解決策Aを使用することもできます。
更新、2019-05-01: 現在の "最悪最悪の慣行"は MSDNの記事にあります 。
最終的に私の問題を解決した解決策を追加すると、誰かの時間を節約できることを願っています。
最初に Stephen Cleary の記事をいくつか読んでください:
「非同期コードでブロックしない」の「2つのベストプラクティス」から、最初の方法は機能せず、2番目の方法は適用できませんでした(基本的にawait
を使用できる場合は、機能します! )。
だからここに私の回避策があります:Task.Run<>(async () => await FunctionAsync());
の内側に呼び出しをラップし、うまくいけばdeadlockはもうありません。
ここに私のコードがあります:
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は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));
}
コードのライセンス条件について懸念がある方のために、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*/
}
これは、多くのMicrosoftのドキュメントで使用されています。たとえば、次のようになります。 https://docs.Microsoft.com/ja-jp/Azure/service-bus-messaging/service-bus-dotnet-how-to- use-topics-subscriptions
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'というキーワードを「この長時間実行タスクを開始してから呼び出し元のメソッドに制御を戻す」と読みます。長期実行タスクが完了すると、それはその後にコードを実行します。待機後のコードは、以前CallBackメソッドだったものと似ています。論理フローという大きな違いは中断されないため、書き込みと読み取りがはるかに簡単になります。
100%確信が持てませんが、 このブログ に記載されている手法はさまざまな状況で機能するはずです。
この伝播ロジックを直接呼び出したい場合は、
task.GetAwaiter().GetResult()
を使用できます。
最も受け入れられている答えは完全には正しくありません。そこに IS あらゆる状況で機能する解決策:特別メッセージポンプ(SynchronizationContext)。
Async関数から呼び出されたすべての継続が、呼び出しスレッドで実行されているアドホック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(...));
非同期ポンプのより詳細な説明は利用可能です ここ 。
もうこの質問に注目している人には….
Microsoft.VisualStudio.Services.WebApi
を見ると、TaskExtensions
というクラスがあります。そのクラス内には、静的拡張メソッドTask.SyncResult()
があります。これは、タスクが戻るまでスレッドを完全にブロックするようなものです。
内部的には非常に単純なtask.GetAwaiter().GetResult()
を呼び出しますが、async
、Task<T>
またはTask<HttpResponseMessage>
...を返すTask
メソッドを処理するためにオーバーロードされています。
...GetAwaiter().GetResult()
は、ブロッキングコンテキストで非同期コードを実行するためのMSの公式な方法のようです。私のユースケースではとてもうまくいくようです。
同期コードから任意の非同期メソッドを呼び出すことができます。つまり、それらに対してawait
が必要になるまでです。その場合は、それらもasync
とマークする必要があります。
多くの人がここで提案しているように、同期メソッドの結果のタスクに対してWait()またはResultを呼び出すことができますが、その場合はそのメソッドでブロッキング呼び出しが発生し、非同期の目的が無効になります。
メソッドをasync
にすることはできませんし、同期メソッドをロックしたくない場合は、タスクのContinueWithメソッドにパラメータとして渡してコールバックメソッドを使用する必要があります。
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);
OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();
またはこれを使う:
var result=result.GetAwaiter().GetResult().AccessToken
私は私がとても遅れているのを知っています。しかし、私のような人が、他の図書館に頼らずに、きちんとした簡単な方法でこれを解決したいと思った場合。
次のコードが見つかりました コードの一部 from Ryan
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>());