Mengapa saya harus membuat operasi WebAPI asinkron dan bukan yang sinkron?

109

Saya memiliki operasi berikut di Web API yang saya buat:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public CartTotalsDTO GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh);
}

Panggilan ke layanan web ini dilakukan melalui panggilan Jquery Ajax dengan cara ini:

$.ajax({
      url: "/api/products/pharmacies/<%# Farmacia.PrimaryKeyId.Value.ToString() %>/page/" + vm.currentPage() + "/" + filter,
      type: "GET",
      dataType: "json",
      success: function (result) {
          vm.items([]);
          var data = result.Products;
          vm.totalUnits(result.TotalUnits);
      }          
  });

Saya telah melihat beberapa pengembang yang menerapkan operasi sebelumnya dengan cara ini:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return await Task.Factory.StartNew(() => delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh));
}

Namun, harus dikatakan, bahwa GetProductsWithHistory () adalah operasi yang cukup lama. Mengingat masalah dan konteks saya, bagaimana membuat operasi webAPI asynchronous menguntungkan saya?

David Jiménez Martínez
sumber
1
Sisi klien menggunakan AJAX, yang sudah asinkron. Anda tidak perlu layanan juga ditulis sebagai file async Task<T>. Ingat, AJAX diimplementasikan bahkan sebelum TPL ada :)
Dominic Zukiewicz
65
Anda perlu memahami mengapa Anda menerapkan pengontrol asinkron, banyak yang tidak. IIS memiliki jumlah thread yang terbatas dan ketika semua sedang digunakan server tidak dapat memproses permintaan baru. Dengan pengontrol async saat sebuah proses menunggu I / O selesai, utasnya dibebaskan untuk digunakan server untuk memproses permintaan lain.
Matija Grcic
3
Pengembang apa yang Anda lihat melakukan itu? Jika ada posting blog atau artikel yang merekomendasikan teknik itu, silakan posting tautannya.
Stephen Cleary
3
Anda hanya mendapatkan manfaat penuh dari asinkron jika proses Anda sadar asinkron dari atas (termasuk aplikasi web itu sendiri dan pengontrol Anda) hingga aktivitas menunggu apa pun yang terjadi di luar proses Anda (termasuk penundaan pengatur waktu, I / O file, akses DB, dan permintaan web yang dibuatnya). Dalam kasus ini, pembantu delegasi Anda perlu GetProductsWithHistoryAsync()kembali Task<CartTotalsDTO>. Ada keuntungan untuk menulis async controller Anda jika Anda bermaksud untuk memigrasi panggilan yang dibuatnya menjadi async juga; kemudian Anda mulai mendapatkan manfaat dari bagian asinkron saat Anda memigrasi sisanya.
Keith Robertson
1
Jika proses yang Anda lakukan tidak aktif dan mencapai database, maka utas web Anda hanya menunggu untuk mendapatkan kembali dan menahan utas itu. Jika Anda telah mencapai jumlah utas maksimal dan permintaan lain masuk, itu harus menunggu. Mengapa demikian? Sebaliknya Anda ingin membebaskan utas itu dari pengontrol Anda sehingga permintaan lain dapat menggunakannya dan hanya mengambil utas web lain ketika permintaan asli Anda dari database kembali. msdn.microsoft.com/en-us/magazine/dn802603.aspx
pengguna441521

Jawaban:

98

Dalam contoh spesifik Anda, operasi tersebut sama sekali tidak asinkron sehingga yang Anda lakukan adalah asinkron melalui sinkronisasi. Anda baru saja melepaskan satu utas dan memblokir utas lainnya. Tidak ada alasan untuk itu, karena semua utas adalah utas kumpulan utas (tidak seperti dalam aplikasi GUI).

Dalam diskusi saya tentang "async over sync," saya sangat menyarankan bahwa jika Anda memiliki API yang secara internal diterapkan secara sinkron, Anda tidak boleh mengekspos mitra asinkron yang hanya membungkus metode sinkron Task.Run.

Dari Haruskah saya mengekspos pembungkus sinkron untuk metode asinkron?

Namun saat membuat panggilan WebAPI di asyncmana ada operasi asinkron yang sebenarnya (biasanya I / O) alih-alih memblokir utas yang duduk dan menunggu hasil, utas kembali ke kumpulan utas dan dengan demikian dapat melakukan beberapa operasi lain. Secara keseluruhan, itu berarti bahwa aplikasi Anda dapat melakukan lebih banyak hal dengan sumber daya yang lebih sedikit dan meningkatkan skalabilitas.

i3arnon
sumber
3
@efaruk semua utas adalah utas pekerja. Merilis satu utas ThreadPool dan memblokir utas lainnya tidak ada gunanya.
i3arnon
1
@efaruk Saya tidak yakin apa yang Anda coba katakan .. tapi selama Anda setuju tidak ada alasan untuk menggunakan async melalui sinkronisasi di WebAPI maka tidak masalah.
i3arnon
@efaruk "async over sync" (yaitu await Task.Run(() => CPUIntensive())) tidak berguna di asp.net. Anda tidak mendapatkan apa-apa dari melakukan itu. Anda baru saja melepaskan satu utas ThreadPool untuk menempati utas lainnya. Ini kurang efisien daripada hanya memanggil metode sinkron.
i3arnon
1
@efaruk Tidak, itu tidak masuk akal. Anda contoh menjalankan tugas independen secara berurutan. Anda benar-benar perlu membaca tentang asyc / await sebelum membuat rekomendasi. Anda perlu menggunakan await Task.WhenAlluntuk mengeksekusi secara paralel.
Søren Boisen
1
@efaruk Seperti yang dijelaskan Boisen, contoh Anda tidak menambahkan nilai apa pun selain hanya memanggil metode sinkron ini secara berurutan. Anda dapat menggunakan Task.Runjika Anda ingin memparalelkan beban Anda pada beberapa utas, tetapi bukan itu yang dimaksud dengan "asinkron melalui sinkronisasi". Referensi "async over sync" membuat metode asinkron sebagai pembungkus di atas metode sinkron. Anda bisa melihat kutipan di jawaban saya.
i3arnon
1

Salah satu pendekatannya adalah (Saya telah berhasil menggunakan ini dalam aplikasi pelanggan) agar Layanan Windows menjalankan operasi yang panjang dengan utas pekerja, dan kemudian melakukan ini di IIS untuk membebaskan utas hingga operasi pemblokiran selesai: Catatan, ini menganggap hasil disimpan dalam tabel (baris diidentifikasi oleh jobId) dan proses pembersihan membersihkannya beberapa jam setelah digunakan.

Untuk menjawab pertanyaan, "Mengingat masalah dan konteks saya, bagaimana membuat operasi webAPI asynchronous menguntungkan saya?" mengingat bahwa ini adalah "operasi yang cukup lama". Saya memikirkan beberapa detik daripada ms, pendekatan ini membebaskan utas IIS. Jelas Anda juga harus menjalankan layanan windows yang dengan sendirinya mengambil sumber daya tetapi pendekatan ini dapat mencegah banjir kueri yang lambat untuk mencuri utas dari bagian lain sistem.

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
        var jobID = Guid.NewGuid().ToString()
        var job = new Job
        {
            Id = jobId,
            jobType = "GetProductsWithHistory",
            pharmacyId = pharmacyId,
            page = page,
            filter = filter,
            Created = DateTime.UtcNow,
            Started = null,
            Finished = null,
            User =  {{extract user id in the normal way}}
        };
        jobService.CreateJob(job);

        var timeout = 10*60*1000; //10 minutes
        Stopwatch sw = new Stopwatch();
        sw.Start();
        bool responseReceived = false;
        do
        {
            //wait for the windows service to process the job and build the results in the results table
            if (jobService.GetJob(jobId).Finished == null)
            {
                if (sw.ElapsedMilliseconds > timeout ) throw new TimeoutException();
                await Task.Delay(2000);
            }
            else
            {
                responseReceived = true;
            }
        } while (responseReceived == false);

    //this fetches the results from the temporary results table
    return jobService.GetProductsWithHistory(jobId);
}

sumber