web-dev-qa-db-ja.com

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

asyncメソッドがあります。

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

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

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

アップデート

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

しかし、私はHttpClientがすでにこれを実装しているのを見ます pattern

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

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

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

string code = GenerateCodeAsync().Result;

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

248
Heinzi

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

string code = GenerateCodeAsync().GetAwaiter().GetResult();
50
Diego Torres

あなたはデリゲート、ラムダ式を使ってこれを成し遂げることができるはずです

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" ;
    }
28
Faiyaz

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

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

しかし、あなたの質問は asp.net でタグ付けされていて、あなたもコメントを残しました:

Asp.netが非常に多くのコード行を書くよりはるかに簡単にこれを処理すると考えて、私はもっと簡単な解決策を望んでいました。

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

ASP.NETは、非同期コントローラ(ASP.NET MVCおよびWeb API)、または従来のASP.NETのAsyncManagerおよびPageAsyncTaskを介して、非同期メソッドを組み込みでサポートしています。あなたはそれを使うべきです。詳しくは この答え をチェックしてください。

20
noseratio

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();
16

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

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

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

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

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

他の方法は、タスクが終了するまで待つことです。

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