Memanggil metode async secara sinkron

231

Saya punya asyncmetode:

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

Saya perlu memanggil metode ini dari metode sinkron.

Bagaimana saya bisa melakukan ini tanpa harus menduplikasi GenerateCodeAsyncmetode agar ini bekerja secara serempak?

Memperbarui

Namun tidak ada solusi masuk akal yang ditemukan.

Namun, saya melihat bahwa HttpClientsudah menerapkan pola ini

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

    // sync
    HttpResponseMessage responseSync = client.GetAsync(url).Result;
}
Catalin
sumber
1
Saya berharap untuk solusi yang lebih sederhana, berpikir bahwa asp.net menangani ini jauh lebih mudah daripada menulis begitu banyak baris kode
Catalin
Mengapa tidak menerima kode async saja? Idealnya Anda menginginkan lebih banyak kode async, bukan kurang.
Paulo Morgado
54
[Mengapa tidak hanya merangkul kode async?] Ha, mungkin justru karena seseorang merangkul kode async bahwa mereka memerlukan solusi ini karena sebagian besar proyek dikonversi! Anda tidak dapat membangun kembali Roma dalam sehari.
Nicholas Petersen
1
@NicholasPetersen terkadang pustaka pihak ketiga dapat memaksa Anda untuk melakukan ini. Contoh membangun pesan dinamis dalam metode WithMessage dari FluentValidation. Tidak ada API async untuk ini karena desain perpustakaan - WithMessage overload bersifat statis. Metode lain untuk meneruskan argumen dinamis ke WithMessage aneh.
H.Wojtowicz

Jawaban:

278

Anda dapat mengakses Resultproperti tugas, yang akan menyebabkan utas Anda memblokir hingga hasilnya tersedia:

string code = GenerateCodeAsync().Result;

Catatan: Dalam beberapa kasus, ini dapat menyebabkan kebuntuan: Panggilan Anda untuk Resultmemblokir utas utama, sehingga mencegah sisa kode async untuk dieksekusi. Anda memiliki opsi berikut untuk memastikan bahwa ini tidak terjadi:

Ini tidak berarti bahwa Anda hanya perlu menambahkan tanpa sadar .ConfigureAwait(false)setelah semua panggilan async Anda! Untuk analisis terperinci tentang mengapa dan kapan Anda harus menggunakan .ConfigureAwait(false), lihat posting blog berikut:

Heinzi
sumber
33
Jika memohon resultrisiko kebuntuan, maka ketika adalah aman untuk mendapatkan hasilnya? Apakah setiap panggilan tidak sinkron memerlukan Task.Runatau ConfigureAwait(false)?
Robert Harvey
4
Tidak ada "utas utama" di ASP.NET (tidak seperti aplikasi GUI), tetapi kebuntuan masih mungkin karena bagaimana AspNetSynchronizationContext.Post serialisasi kelanjutan async:Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
noseratio
4
@RobertHarvey: Jika Anda tidak memiliki kendali atas implementasi metode async yang Anda blokir, maka ya, Anda harus membungkusnya dengan Task.Runagar tetap aman. Atau gunakan sesuatu seperti WithNoContextuntuk mengurangi penggantian ulir yang berlebihan.
noseratio
10
CATATAN: Memanggil .Resultmasih bisa menemui jalan buntu jika pemanggil berada di kumpulan utas itu sendiri. Ambil skenario di mana Thread Pool berukuran 32 dan 32 tugas sedang berjalan dan Wait()/Resultmenunggu tugas ke-33 yang belum dijadwalkan yang ingin dijalankan di salah satu utas menunggu.
Warty
55

Anda harus mendapatkan penunggu ( GetAwaiter()) dan mengakhiri menunggu untuk penyelesaian tugas asinkron ( GetResult()).

string code = GenerateCodeAsync().GetAwaiter().GetResult();
Diego Torres
sumber
38
Kami mengalami kebuntuan menggunakan solusi ini. Diperingatkan.
Oliver
6
MSDNTask.GetAwaiter : Metode ini dimaksudkan untuk penggunaan kompiler daripada untuk digunakan dalam kode aplikasi.
foka
Saya masih mendapatkan popup Dialog kesalahan (bertentangan dengan keinginan saya), dengan tombol 'Switch To' atau 'Retry'…. Namun, panggilan itu benar-benar dijalankan dan kembali dengan respon yang tepat.
Jonathan Hansen
30

Anda harus bisa menyelesaikan ini menggunakan delegasi, ekspresi lambda

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" ;
    }
Faiyaz
sumber
Cuplikan ini tidak akan dikompilasi. Jenis kembali dari Task.Run adalah Task. Lihat blog MSDN ini untuk penjelasan lengkap.
Appetere
5
Terima kasih telah menunjukkan, ya itu mengembalikan jenis Tugas. Mengganti "string sCode" ke Task <string> atau var sCode harus menyelesaikannya. Menambahkan kode kompilasi penuh untuk memudahkan.
Faiyaz
20

Saya perlu memanggil metode ini dari metode yang sinkron.

Mungkin dengan GenerateCodeAsync().Resultatau GenerateCodeAsync().Wait(), seperti jawaban yang disarankan. Ini akan memblokir utas saat ini sampai GenerateCodeAsyncselesai.

Namun, pertanyaan Anda ditandai dengan , dan Anda juga meninggalkan komentar:

Saya berharap untuk solusi yang lebih sederhana, berpikir bahwa asp.net menangani ini lebih mudah daripada menulis begitu banyak baris kode

Maksud saya adalah, Anda seharusnya tidak memblokir metode asinkron dalam ASP.NET. Ini akan mengurangi skalabilitas aplikasi web Anda, dan dapat membuat jalan buntu (ketika awaitkelanjutan di dalam GenerateCodeAsyncdiposting ke AspNetSynchronizationContext). Menggunakan Task.Run(...).Resultuntuk melepas sesuatu ke thread pool dan kemudian memblokir skalabilitas akan semakin menyakitkan, karena ia membuat +1 lebih banyak thread untuk memproses permintaan HTTP yang diberikan.

ASP.NET memiliki dukungan bawaan untuk metode asinkron, baik melalui pengontrol asinkron (dalam ASP.NET MVC dan Web API) atau langsung melalui AsyncManagerdan PageAsyncTaskdalam ASP.NET klasik. Anda harus menggunakannya. Untuk lebih jelasnya, periksa jawaban ini .

noseratio
sumber
Saya menimpa SaveChanges()metode DbContext, dan di sini saya memanggil metode async, jadi sayangnya kontroler async tidak akan membantu saya dalam situasi ini
Catalin
3
@ RaraituL, secara umum, Anda tidak mencampur kode async dan sinkronisasi, pilih model euther Anda dapat mengimplementasikan keduanya SaveChangesAsyncdan SaveChanges, pastikan mereka tidak dipanggil keduanya dalam proyek ASP.NET yang sama.
noseratio
4
Tidak semua .NET MVCfilter mendukung kode asinkron IAuthorizationFilter, jadi saya tidak bisa menggunakan asyncsemuanya
Catalin
3
@Noseratio itu adalah tujuan yang tidak realistis. Ada terlalu banyak pustaka dengan kode asinkron dan sinkron serta situasi di mana hanya menggunakan satu model tidak mungkin. MVC ActionFilters tidak mendukung kode asinkron, misalnya.
Justin Skiles
9
@Noserato, pertanyaannya adalah tentang memanggil metode asinkron dari sinkron. Terkadang Anda tidak dapat mengubah API yang Anda laksanakan. Katakanlah Anda menerapkan beberapa antarmuka sinkron dari kerangka kerja pihak ketiga "A" (Anda tidak dapat menulis ulang kerangka kerja dengan cara asinkron) tetapi pustaka pihak ketiga "B" yang Anda coba gunakan dalam implementasi Anda hanya memiliki sinkronisasi. Produk yang dihasilkan juga perpustakaan dan dapat digunakan di mana saja termasuk ASP.NET dll
dimzon
19

Microsoft Identity memiliki metode ekstensi yang memanggil metode async secara sinkron. Misalnya ada metode GenerateUserIdentityAsync () dan CreateIdentity () yang sama

Jika Anda melihat UserManagerExtensions.CreateIdentity () terlihat seperti ini:

 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));
    }

Sekarang mari kita lihat apa yang dilakukan 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();
    }

Jadi, ini adalah pembungkus Anda untuk metode async. Dan tolong jangan membaca data dari Hasil - ini berpotensi memblokir kode Anda di ASP.

Ada cara lain - yang mencurigakan bagi saya, tetapi Anda dapat mempertimbangkannya juga

  Result r = null;

            YourAsyncMethod()
                .ContinueWith(t =>
                {
                    r = t.Result;
                })
                .Wait();
Vitaliy Markitanov
sumber
3
Apa yang Anda anggap sebagai masalah dengan cara kedua yang Anda sarankan?
David Clarke
@ Davidvidark mungkin masalah keamanan utas mengakses variabel non-volatil dari beberapa utas tanpa kunci.
Theodor Zoulias
9

Untuk mencegah kebuntuan, saya selalu mencoba menggunakan Task.Run()ketika saya harus memanggil metode async secara serentak yang @Heinzi sebutkan.

Namun metode tersebut harus dimodifikasi jika metode async menggunakan parameter. Misalnya Task.Run(GenerateCodeAsync("test")).Resultmemberi kesalahan:

Argumen 1: tidak dapat dikonversi dari ' System.Threading.Tasks.Task<string>' ke 'System.Action'

Ini bisa disebut seperti ini sebagai gantinya:

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

Sebagian besar jawaban di utas ini rumit atau akan mengakibatkan kebuntuan.

Metode berikut ini sederhana dan akan menghindari kebuntuan karena kami menunggu tugas selesai dan baru kemudian mendapatkan hasilnya

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

Selanjutnya, berikut ini adalah referensi ke artikel MSDN yang berbicara tentang hal yang persis sama- https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-use-task-result- dalam konteks utama /

selamat tinggal
sumber
1

Saya lebih suka pendekatan yang tidak menghalangi:

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

Saya menggunakan pendekatan ini:

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

        return task.Result;
    }
Jiří Herník
sumber
-1

Cara lain bisa jadi jika Anda ingin menunggu sampai tugas selesai:

var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;
frablaser
sumber
1
Itu salah. WhenAll juga mengembalikan Tugas, yang tidak Anda tunggu.
Robert Schmidt
-1

EDIT:

Tugas memiliki metode Tunggu, Task.Wait (), yang menunggu "janji" untuk menyelesaikan dan kemudian melanjutkan, sehingga menjadikannya sinkron. contoh:


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

String mySyncMethod() {

    return MyAsyncMethod().Wait();
}
Avi Tshuva
sumber
3
Mohon uraikan jawaban Anda. Bagaimana ini digunakan? Seberapa spesifik membantu menjawab Pertanyaan?
Scratte
-2

Jika Anda memiliki metode async yang disebut " RefreshList ", Anda dapat memanggil metode async dari metode non-async seperti di bawah ini.

Task.Run(async () => { await RefreshList(); });
dush88c
sumber