非同期メソッドを同期的に呼び出す


231

私にはasync方法があります:

public async Task<string> GenerateCodeAsync()
{
    string code = await GenerateCodeService.GenerateCodeAsync();
    return code;
}

このメソッドを同期メソッドから呼び出す必要があります。

GenerateCodeAsyncこれを同期的に機能させるために、メソッドを複製せずにこれを行うにはどうすればよいですか?

更新

しかし、合理的な解決策は見つかりませんでした。

しかし、私はHttpClientすでにこのパターンを実装していることがわかります

using (HttpClient client = new HttpClient())
{
    // async
    HttpResponseMessage responseAsync = await client.GetAsync(url);

    // sync
    HttpResponseMessage responseSync = client.GetAsync(url).Result;
}


1
asp.netはこれだけ多くのコード行を記述するよりもはるかに簡単に処理できると考えて、より単純な解決策を望んでいました
Catalin

なぜ非同期コードを採用しないのですか?理想的には、非同期ではなく、より多くの非同期コードが必要です。
Paulo Morgado 2014年

54
[なぜ非同期コードを採用するだけではないのですか?]は、プロジェクトの大部分が変換されるときにこのソリューションが必要になるのは、非同期コードを採用しているからかもしれません。一日でローマを再建することはできません。
ニコラスピーターセン2014

1
@NicholasPetersenによっては、サードパーティのライブラリが強制的に実行する場合があります。FluentValidationからWithMessageメソッドで動的メッセージを作成する例。ライブラリの設計により、非同期APIはありません-WithMessageオーバーロードは静的です。WithMessageに動的引数を渡す他のメソッドは奇妙です。
H.Wojtowicz

回答:


278

Resultタスクのプロパティにアクセスできます。これにより、結果が利用可能になるまでスレッドがブロックされます。

string code = GenerateCodeAsync().Result;

注:場合によっては、これによりデッドロックが発生する可能性がありResultます。メインスレッドをブロックする呼び出しにより、残りの非同期コードが実行されなくなります。これが起こらないようにするには、次のオプションがあります。

これ.ConfigureAwait(false)、すべての非同期呼び出しの後に気をつけずに追加する必要があるという意味ではありません。を使用する理由とタイミングの詳細な分析.ConfigureAwait(false)については、次のブログ投稿を参照してください。


33
呼び出しresultがデッドロックの危険を冒している場合、結果を取得するのいつ安全ですか?すべての非同期呼び出しには、Task.Runまたはが必要ですかConfigureAwait(false)
ロバートハーベイ

4
そこ(GUIアプリとは異なり)ASP.NETには「メインスレッドは」ありませんが、デッドロックが理由でまだ可能である AspNetSynchronizationContext.Post非同期継続をシリアライズ:Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
noseratio

4
@RobertHarvey:ブロックしているasyncメソッドの実装を制御できない場合は、はい、それをラップしてTask.Run安全を確保する必要があります。またはWithNoContext、冗長なスレッド切り替えを減らすようなものを使用します。
noseratio

10
注:呼び出し.Result元がスレッドプール自体にいる場合、呼び出しは依然としてデッドロックを起こす可能性があります。スレッドプールのサイズが32であり、32のタスクが実行Wait()/Result中であり、待機中のスレッドの1つで実行したい、まだスケジュールされていない33番目のタスクを待機しているシナリオを考えてみます。
Warty

55

awaiter(GetAwaiter())を取得し、非同期タスクの完了を待つ()必要がありますGetResult()

string code = GenerateCodeAsync().GetAwaiter().GetResult();

38
このソリューションを使用してデッドロックが発生しました。警告されます。
オリバー

6
MSDNTask.GetAwaiter:このメソッドは、アプリケーションコードでの使用ではなく、コンパイラの使用を目的としています。
foka

まだエラーダイアログポップアップが表示されます(私の意志に対して)、ボタン '切り替え先'または '再試行'…があります。ただし、呼び出しは実際に実行され、適切な応答で戻ります。
ジョナサンハンセン

30

デリゲート、ラムダ式を使用してこれを行うことができるはずです

private void button2_Click(object sender, EventArgs e)
    {

        label1.Text = "waiting....";

        Task<string> sCode = Task.Run(async () =>
        {
            string msg =await GenerateCodeAsync();
            return msg;
        });

        label1.Text += sCode.Result;

    }

    private Task<string> GenerateCodeAsync()
    {
        return Task.Run<string>(() => GenerateCode());
    }

    private string GenerateCode()
    {
        Thread.Sleep(2000);
        return "I m back" ;
    }

このスニペットはコンパイルされません。Task.Runからの戻り値の型はTaskです。詳細については、このMSDNブログを参照してください。
Appetere 2015

5
ご指摘いただきありがとうございます。はい、タスクタイプを返します。「string sCode」をTask <string>またはvar sCodeに置き換えると解決されます。完全なコンパイルコードを簡単に追加します。
Faiyaz

20

このメソッドを同期メソッドから呼び出す必要があります。

他の答えが示唆するように、GenerateCodeAsync().ResultまたはGenerateCodeAsync().Wait()で可能です。これにより、GenerateCodeAsyncが完了するまで現在のスレッドがブロックされます。

ただし、あなたの質問には 、あなたもコメントを残しました:

asp.netはこれだけ多くのコード行を書くよりもはるかに簡単に処理できると思って、もっと簡単な解決策を望んでいました

私の要点は、ASP.NETの非同期メソッドをブロックするべきではないということです。これにより、Webアプリのスケーラビリティが低下し、デッドロックが発生する可能性があります(await内部に継続GenerateCodeAsyncがに投稿された場​​合AspNetSynchronizationContext)。を使用Task.Run(...).Resultしてプールスレッドに何かをオフロードしてからブロックすると、特定のHTTPリクエストを処理するために+1のスレッドが追加されるため、スケーラビリティがさらに低下します。

ASP.NETには、非同期コントローラー(ASP.NET MVCおよびWeb API内)を介してAsyncManager、またはPageAsyncTaskクラシックASP.NETを介して直接、非同期メソッドの組み込みサポートがあります。あなたはそれを使うべきです。詳細については、この回答を確認してください。


私はのSaveChanges()メソッドを上書きしています。DbContextここでは非同期メソッドを呼び出しているので、残念ながら非同期コントローラーはこの状況で私を助けません
Catalin

3
@RaraituLは、一般的に、非同期コードと同期コードを混在させず、他のモデルを選択します。SaveChangesAsyncとの両方を実装できSaveChangesます。同じASP.NETプロジェクトで両方が呼び出されないようにしてください。
noseratio 14年

4
.NET MVCたとえばIAuthorizationFilter、すべてのフィルターが非同期コードをサポートしているわけではないため、asyncすべてを使用することはできません
Catalin

3
@Noseratioは非現実的な目標です。非同期コードと同期コードを含むライブラリが多すぎ、モデルを1つだけしか使用できない場合があります。たとえば、MVC ActionFilterは非同期コードをサポートしていません。
ジャスティンスキレス

9
@Noserato、問題は同期から非同期メソッドを呼び出すことです。実装しているAPIを変更できない場合があります。サードパーティのフレームワーク「A」から同期インターフェースを実装しているとします(フレームワークを非同期に書き換えることはできません)が、実装で使用しようとしているサードパーティのライブラリ「B」は非同期のみです。また、結果として生成される製品もライブラリであり、ASP.NETなどを含むあらゆる場所で使用できます
dimzon

19

Microsoft Identityには、非同期メソッドを同期的に呼び出す拡張メソッドがあります。たとえば、GenerateUserIdentityAsync()メソッドと等しいCreateIdentity()があります。

UserManagerExtensions.CreateIdentity()を見ると、次のようになります。

 public static ClaimsIdentity CreateIdentity<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user,
        string authenticationType)
        where TKey : IEquatable<TKey>
        where TUser : class, IUser<TKey>
    {
        if (manager == null)
        {
            throw new ArgumentNullException("manager");
        }
        return AsyncHelper.RunSync(() => manager.CreateIdentityAsync(user, authenticationType));
    }

AsyncHelper.RunSyncが何をするかを見てみましょう

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

したがって、これは非同期メソッドのラッパーです。また、結果からデータを読み取らないでください。ASPでコードがブロックされる可能性があります。

別の方法があります-私には不審ですが、あなたもそれを考慮することができます

  Result r = null;

            YourAsyncMethod()
                .ContinueWith(t =>
                {
                    r = t.Result;
                })
                .Wait();

3
あなたが提案した2番目の方法の問題は何だと思いますか?
デビッドクラーク

@DavidClarkeはおそらく、ロックなしで複数のスレッドから不揮発性変数にアクセスすることのスレッドセーフの問題です。
Theodor Zoulias

9

デッドロックを防ぐためにTask.Run()、@ Heinziが言及している非同期メソッドを同期的に呼び出す必要がある場合は、常に使用を試みます。

ただし、非同期メソッドがパラメーターを使用する場合は、メソッドを変更する必要があります。たとえばTask.Run(GenerateCodeAsync("test")).Resultエラーが発生します:

引数1: ' System.Threading.Tasks.Task<string>'から 'System.Action'に変換できません

代わりに、次のように呼び出すことができます。

string code = Task.Run(() => GenerateCodeAsync("test")).Result;

6

このスレッドの回答のほとんどは複雑であるか、デッドロックを引き起こします。

次の方法は簡単で、タスクが完了するのを待ってから結果を取得するだけなので、デッドロックを回避できます-

var task = Task.Run(() => GenerateCodeAsync()); 
task.Wait();
string code = task.Result;

さらに、これはまったく同じことについて話しているMSDN記事への参照です-https : //blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result- in-main-context /


1

私はノンブロッキングアプローチを好む:

            Dim aw1=GenerateCodeAsync().GetAwaiter()
            While Not aw1.IsCompleted
                Application.DoEvents()
            End While

0

さて、私はこのアプローチを使用しています:

    private string RunSync()
    {
        var task = Task.Run(async () => await GenerateCodeService.GenerateCodeAsync());
        if (task.IsFaulted && task.Exception != null)
        {
            throw task.Exception;
        }

        return task.Result;
    }

-1

もう1つの方法は、タスクが完了するまで待機する場合です。

var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;

1
それは明らかに間違っています。WhenAllは、待機していないタスクも返します。
ロバートシュミット

-1

編集:

TaskにはWaitメソッドTask.Wait()があり、これは「約束」が解決するのを待ってから続行し、同期的にレンダリングします。例:


async Task<String> MyAsyncMethod() { ... }

String mySyncMethod() {

    return MyAsyncMethod().Wait();
}

3
回答について詳しく説明してください。使い方は?質問に答えることは具体的にどのように役立ちますか?
スクラッチ

-2

RefreshList」という非同期メソッドがある場合は、以下のように非同期メソッドからその非同期メソッドを呼び出すことができます。

Task.Run(async () => { await RefreshList(); });
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.