Jika antarmuka saya harus mengembalikan Tugas, apa cara terbaik untuk mengimplementasikan implementasi tanpa operasi?

436

Dalam kode di bawah ini, karena antarmuka, kelas LazyBarharus mengembalikan tugas dari metode itu (dan demi argumen tidak dapat diubah). Jika LazyBarimplementasi tidak biasa terjadi karena berjalan dengan cepat dan serempak - apa cara terbaik untuk mengembalikan tugas Tanpa Operasi dari metode ini?

Saya telah membahas di Task.Delay(0)bawah ini, namun saya ingin tahu apakah ini memiliki efek samping kinerja jika fungsinya disebut banyak (demi argumen, katakan ratusan kali per detik):

  • Apakah gula sintaksis ini melepaskan angin ke sesuatu yang besar?
  • Apakah itu mulai menyumbat kumpulan utas aplikasi saya?
  • Apakah golok compiler cukup untuk menangani secara Delay(0)berbeda?
  • Apakah return Task.Run(() => { });akan berbeda?

Apakah ada cara yang lebih baik?

using System.Threading.Tasks;

namespace MyAsyncTest
{
    internal interface IFooFace
    {
        Task WillBeLongRunningAsyncInTheMajorityOfImplementations();
    }

    /// <summary>
    /// An implementation, that unlike most cases, will not have a long-running
    /// operation in 'WillBeLongRunningAsyncInTheMajorityOfImplementations'
    /// </summary>
    internal class LazyBar : IFooFace
    {
        #region IFooFace Members

        public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
        {
            // First, do something really quick
            var x = 1;

            // Can't return 'null' here! Does 'Task.Delay(0)' have any performance considerations?
            // Is it a real no-op, or if I call this a lot, will it adversely affect the
            // underlying thread-pool? Better way?
            return Task.Delay(0);

            // Any different?
            // return Task.Run(() => { });

            // If my task returned something, I would do:
            // return Task.FromResult<int>(12345);
        }

        #endregion
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Test();
        }

        private static async void Test()
        {
            IFooFace foo = FactoryCreate();
            await foo.WillBeLongRunningAsyncInTheMajorityOfImplementations();
            return;
        }

        private static IFooFace FactoryCreate()
        {
            return new LazyBar();
        }
    }
}
Jon Rea
sumber
8
Secara pribadi saya akan pergi Task.FromResult<object>(null).
CodesInChaos

Jawaban:

626

Menggunakan Task.FromResult(0)atau Task.FromResult<object>(null)akan dikenakan biaya lebih sedikit daripada membuat Taskdengan tanpa-op ekspresi. Saat membuat Taskdengan hasil yang ditentukan sebelumnya, tidak ada overhead penjadwalan yang terlibat.


Hari ini, saya akan merekomendasikan menggunakan Task.CompletedTask untuk mencapai ini.

Reed Copsey
sumber
5
Dan jika Anda menggunakan github.com/StephenCleary/AsyncEx, mereka menyediakan kelas TaskConstants untuk menyediakan tugas-tugas yang diselesaikan bersama dengan beberapa yang cukup berguna lainnya (0 int, true / false, Default <T> ())
quentin-starin
5
return default(YourReturnType);
Legenda
8
@Legends Itu tidak berfungsi untuk membuat Tugas secara langsung
Reed Copsey
18
Saya tidak yakin tetapi Task.CompletedTaskmungkin melakukan trik! (tetapi membutuhkan .net 4.6)
Peter
187

Untuk menambahkan jawaban Reed Copsey tentang penggunaan Task.FromResult, Anda dapat meningkatkan kinerja lebih banyak lagi jika Anda men-cache tugas yang sudah selesai karena semua contoh tugas yang diselesaikan adalah sama:

public static class TaskExtensions
{
    public static readonly Task CompletedTask = Task.FromResult(false);
}

Dengan TaskExtensions.CompletedTaskAnda dapat menggunakan contoh yang sama di seluruh domain aplikasi.


Versi terbaru dari .Net Framework (v4.6) menambahkan hanya itu dengan Task.CompletedTaskproperti statis

Task completedTask = Task.CompletedTask;
i3arnon
sumber
Apakah saya perlu mengembalikannya atau menunggu ?
Pixar
@ Pixar apa maksudmu? Anda dapat melakukan keduanya, tetapi menunggu itu akan berlanjut secara serempak.
i3arnon
Maaf, saya harus menyebutkan konteksnya :) Seperti yang saya lihat sekarang, kita bisa membuatnya public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()juga public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Jadi, kita bisa return CompletedTask;atau await CompletedTask;. Apa yang lebih disukai (mungkin lebih efisien atau lebih kongruen)?
Pixar
3
@Pixar Saya tidak jelas. Maksud saya "'no-async' akan lebih efisien". Membuat metode async memerintahkan kompiler untuk mengubahnya menjadi mesin negara. Ini juga akan membuat tugas baru setiap kali Anda menyebutnya. Mengembalikan tugas yang sudah selesai akan menjadi lebih jelas dan lebih berkinerja.
i3arnon
3
@Asad mengurangi alokasi (dan dengan itu waktu GC). Alih-alih mengalokasikan memori baru dan membuat instance Tugas setiap kali Anda membutuhkan Tugas yang diselesaikan, Anda hanya melakukan ini sekali.
i3arnon
38

Task.Delay(0)seperti dalam jawaban yang diterima adalah pendekatan yang baik, karena merupakan salinan cache yang sudah diisi Task.

Pada 4.6 ada sekarang Task.CompletedTaskyang lebih eksplisit dalam tujuannya, tetapi tidak hanya Task.Delay(0)masih kembali contoh cache tunggal, itu mengembalikan contoh cache tunggal yang sama seperti halnya Task.CompletedTask.

Sifat cache dari tidak dijamin untuk tetap konstan, tetapi sebagai optimisations tergantung dari implementasi yang hanya tergantung dari implementasi sebagai optimisations (yaitu, mereka masih akan bekerja dengan benar jika pelaksanaan berubah menjadi sesuatu yang masih berlaku) penggunaan Task.Delay(0)adalah lebih baik dari jawaban yang diterima.

Jon Hanna
sumber
1
Saya masih menggunakan 4,5 dan ketika saya melakukan riset, saya merasa geli mendapati bahwa Task.Delay (0) dibuat khusus untuk mengembalikan anggota CompletedTask yang statis. Yang kemudian saya cache di anggota CompletedTask statis saya sendiri. : P
Darren Clark
2
Saya tidak tahu mengapa, tetapi Task.CompletedTasktidak dapat digunakan dalam proyek PCL, bahkan jika saya mengatur versi .net ke 4.6 (profil 7), baru saja diuji dalam VS2017.
Felix
@Fay Saya kira itu tidak boleh menjadi bagian dari permukaan PCL API, meskipun satu-satunya melakukan apa pun dengan saat ini yang mendukung PCL juga mendukung 4,5 jadi saya sudah harus menggunakan saya sendiri Task.CompletedTask => Task.Delay(0);untuk mendukung itu, jadi saya tidak Aku tahu pasti dari atas kepalaku.
Jon Hanna
17

Baru-baru ini menjumpai ini dan terus mendapatkan peringatan / kesalahan tentang metode yang batal.

Kami berada dalam bisnis menempatkan kompiler dan ini akan membersihkannya:

    public async Task MyVoidAsyncMethod()
    {
        await Task.CompletedTask;
    }

Sejauh ini, ini menyatukan semua saran terbaik di sini. Pernyataan pengembalian tidak diperlukan kecuali Anda benar-benar melakukan sesuatu dalam metode ini.

Alexander Trauzzi
sumber
17
Itu sepenuhnya salah. Anda mendapatkan kesalahan kompilator karena definisi metode berisi async, jadi kompilator mengharapkan menunggu. Penggunaan "benar" akan menjadi Tugas publik MyVoidAsyncMethog () {return Task.CompletedTask;}
Keith
3
Tidak yakin mengapa ini tidak dipilih karena ini tampaknya menjadi jawaban terbersih
webwake
3
Karena komentar Keith.
noelicus
4
Dia tidak sepenuhnya salah, dia baru saja menghapus kata kunci async. Pendekatan saya lebih idiomatis. Nya minimalis. Jika tidak sedikit kasar.
Alexander Trauzzi
1
Ini tidak masuk akal, sepenuhnya setuju dengan Keith di sini, saya tidak mendapatkan semua upvotes sebenarnya. Mengapa Anda menambahkan kode yang tidak perlu? public Task MyVoidAsyncMethod() {}sepenuhnya sama dengan metode di atas. Jika ada usecase untuk menggunakannya seperti ini, silakan tambahkan kode tambahan.
Nick N.
12
return Task.CompletedTask; // this will make the compiler happy
Xin
sumber
9

Ketika Anda harus mengembalikan jenis yang ditentukan:

Task.FromResult<MyClass>(null);
pembuat sampah_
sumber
3

Saya lebih suka Task completedTask = Task.CompletedTask;solusi. Net 4.6, tetapi pendekatan lain adalah menandai metode async dan mengembalikan void:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Anda akan mendapatkan peringatan (CS1998 - Async berfungsi tanpa menunggu ekspresi), tetapi ini aman untuk diabaikan dalam konteks ini.

Remco te Wierik
sumber
1
Jika metode Anda mengembalikan batal, Anda dapat memiliki masalah dengan pengecualian.
Adam Tuliper - MSFT
0

Jika Anda menggunakan obat generik, semua jawaban akan memberi kami kesalahan kompilasi. Anda bisa menggunakannya return default(T);. Contoh di bawah ini untuk menjelaskan lebih lanjut.

public async Task<T> GetItemAsync<T>(string id)
            {
                try
                {
                    var response = await this._container.ReadItemAsync<T>(id, new PartitionKey(id));
                    return response.Resource;
                }
                catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
                {

                    return default(T);
                }

            }
Karthikeyan VK
sumber
Mengapa downvote?
Karthikeyan VK
Pertanyaannya bukan tentang metode async :)
Frode Nilsen
0
return await Task.FromResult(new MyClass());
JMH
sumber
3
Sementara kode ini dapat menyelesaikan pertanyaan, termasuk penjelasan tentang bagaimana dan mengapa ini menyelesaikan masalah akan sangat membantu untuk meningkatkan kualitas posting Anda, dan mungkin menghasilkan lebih banyak suara. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, bukan hanya orang yang bertanya sekarang. Harap edit jawaban Anda untuk menambahkan penjelasan dan berikan indikasi tentang batasan dan asumsi apa yang berlaku.
David Buck