Jeśli mój interfejs musi zwrócić zadanie, jaki jest najlepszy sposób wdrożenia bez operacji?

435

W poniższym kodzie, ze względu na interfejs, klasa LazyBarmusi zwrócić zadanie ze swojej metody (i ze względu na argumenty nie można go zmienić). Jeśli LazyBarimplementacja jest niezwykła, ponieważ zdarza się, że działa szybko i synchronicznie - jaki jest najlepszy sposób na zwrócenie zadania braku operacji z metody?

Przeszedłem Task.Delay(0)poniżej, jednak chciałbym wiedzieć, czy ma to jakieś skutki uboczne wydajności, jeśli funkcja jest często wywoływana (na przykład argumenty, powiedzmy setki razy na sekundę):

  • Czy ten cukier składniowy nie rozwija się w coś dużego?
  • Czy zaczyna się zapychać pulę wątków mojej aplikacji?
  • Czy tasak kompilatora jest wystarczający, aby radzić sobie Delay(0)inaczej?
  • Byłoby return Task.Run(() => { });inaczej?

Czy jest lepszy sposób?

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
źródło
8
Osobiście poszedłbym z Task.FromResult<object>(null).
CodesInChaos

Odpowiedzi:

625

Korzystanie z Task.FromResult(0)lub Task.FromResult<object>(null)spowoduje mniejsze koszty ogólne niż tworzenie Taskwyrażenia „no-op”. Podczas tworzenia Taskz wcześniej ustalonym rezultatem nie ma narzutu związanego z planowaniem.


Dzisiaj zaleciłbym użycie Task.CompletedTask, aby to osiągnąć.

Reed Copsey
źródło
5
A jeśli używasz github.com/StephenCleary/AsyncEx , zapewniają klasę TaskConstants, która zapewnia ukończone zadania wraz z kilkoma innymi całkiem przydatnymi (0 int, true / false, Default <T> ())
quentin-starin
5
return default(YourReturnType);
Legendy
8
@ Legends To nie działa w przypadku bezpośredniego tworzenia zadania
Reed Copsey
18
nie jestem pewien, ale Task.CompletedTaskmoże załatwić sprawę! (ale wymaga .net 4.6)
Peter
1
Pytanie: w jaki sposób to Task.FromResult<TResult>, co zwraca a Task<TResult>, spełnia typ zwracanego parametru Task(bez parametrów typu)?
rory.ap
187

Aby dodać do odpowiedzi Reed Copsey na temat używania Task.FromResult, możesz jeszcze bardziej zwiększyć wydajność, jeśli buforujesz już ukończone zadanie, ponieważ wszystkie wystąpienia zakończonych zadań są takie same:

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

Dzięki TaskExtensions.CompletedTaskmożesz używać tego samego wystąpienia w całej domenie aplikacji.


Najnowszą wersję .NET Framework (v4.6) dodaje tylko, że z Task.CompletedTaskwłasności statyczne

Task completedTask = Task.CompletedTask;
i3arnon
źródło
Czy muszę go zwrócić lub poczekać ?
Pixar,
@Pixar co masz na myśli? Możesz zrobić oba, ale oczekiwanie na to będzie kontynuowane synchronicznie.
i3arnon,
Przepraszam, musiałem wspomnieć o kontekście :) Jak widzę teraz, możemy zrobić public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()równie dobrze public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Tak więc, możemy albo return CompletedTask;albo await CompletedTask;. Co jest bardziej preferowane (może bardziej wydajne lub przystające)?
Pixar,
3
@Pixar Nie wiem. Miałem na myśli, że „brak asynchronizacji” byłby bardziej wydajny ”. Wykonanie asynchronicznej metody instruuje kompilator, aby przekształcił go w maszynę stanu. Będzie także tworzyć nowe zadanie za każdym razem, gdy je wywołasz. Zwracanie już ukończonego zadania byłoby jaśniejsze i bardziej wydajne.
i3arnon,
3
@Asad zmniejsza alokacje (a wraz z tym czas GC). Zamiast przydzielać nową pamięć i budować instancję zadania za każdym razem, gdy potrzebujesz ukończonego zadania, robisz to tylko raz.
i3arnon,
38

Task.Delay(0)tak jak w przyjętej odpowiedzi było dobrym podejściem, ponieważ jest to buforowana kopia ukończonej Task.

Począwszy od wersji 4.6 istnieje teraz Task.CompletedTaskbardziej wyraźny cel, ale nie tylko Task.Delay(0)zwraca pojedyncze wystąpienie zapisane w pamięci podręcznej, ale zwraca to samo wystąpienie zapisane w pamięci podręcznej, co robi Task.CompletedTask.

Buforowana charakter nie gwarantuje pozostanie niezmieniony, ale jak optymalizacji zależna od implementacji, które są zależne wyłącznie jako realizacja optymalizacji (to znaczy, że one nadal działać prawidłowo, jeśli realizacja zmieniona na coś, co było nadal aktualne) wykorzystanie Task.Delay(0)było lepsze niż zaakceptowana odpowiedź.

Jon Hanna
źródło
1
Nadal korzystam z wersji 4.5, a kiedy przeprowadziłem badania, z rozbawieniem odkryłem, że Task.Delay (0) ma specjalną obudowę, aby zwrócić statycznego członka CompletedTask. Które następnie buforowałem we własnym statycznym elemencie CompletedTask. : P
Darren Clark,
2
Nie wiem dlaczego, ale Task.CompletedTasknie mogę go użyć w projekcie PCL, nawet jeśli ustawię wersję .net na 4.6 (profil 7), właśnie przetestowany w VS2017.
Felix
@Fay Domyślam się, że nie może być częścią interfejsu API PCL, choć jedyne, co w tej chwili robi, co obsługuje PCL, obsługuje również 4.5, więc już muszę używać własnego, Task.CompletedTask => Task.Delay(0);aby to obsługiwać, więc nie nie wiem na pewno z czubka mojej głowy.
Jon Hanna,
17

Ostatnio się z tym spotkałem i otrzymywałem ostrzeżenia / błędy dotyczące nieważności metody.

Zajmujemy się umieszczaniem kompilatora, a to wyjaśnia:

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

Łączy w sobie najlepsze z dotychczasowych porad. Nie jest wymagana instrukcja return, chyba że faktycznie robisz coś w metodzie.

Alexander Trauzzi
źródło
17
To jest całkowicie złe. Otrzymujesz błąd kompilatora, ponieważ definicja metody zawiera asynchronię, więc kompilator oczekuje na oczekiwanie. „Prawidłowe” użycie byłoby publiczne Zadanie MyVoidAsyncMethog () {return Task.CompletedTask;}
Keith
3
Nie jestem pewien, dlaczego zostało to przegłosowane, ponieważ wydaje się to najczystszą odpowiedzią
webwake
3
Z powodu komentarza Keitha.
noelicus
4
Nie jest całkowicie w błędzie, właśnie usunął słowo kluczowe asynchroniczne. Moje podejście jest bardziej idiotyczne. Jego jest minimalistyczny. Jeśli nie trochę niegrzeczny.
Alexander Trauzzi
1
To nie ma sensu, całkowicie zgadzam się z Keithem tutaj, właściwie nie otrzymuję wszystkich pozytywnych opinii. Dlaczego dodajesz niepotrzebny kod? public Task MyVoidAsyncMethod() {}jest całkowicie taki sam jak powyższa metoda. Jeśli istnieje przypadek użycia go w ten sposób, dodaj dodatkowy kod.
Nick N.,
12
return Task.CompletedTask; // this will make the compiler happy
Xin
źródło
9

Kiedy musisz zwrócić określony typ:

Task.FromResult<MyClass>(null);
trashmaker_
źródło
3

Wolę Task completedTask = Task.CompletedTask;rozwiązanie .Net 4.6, ale innym podejściem jest oznaczenie metody async i return void:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Otrzymasz ostrzeżenie (CS1998 - funkcja asynchroniczna bez oczekiwania na wyrażenie), ale w tym kontekście można to bezpiecznie zignorować.

Remco te Wierik
źródło
1
Jeśli metoda zwróci nieważność, możesz mieć problemy z wyjątkami.
Adam Tuliper - MSFT,
0

Jeśli używasz ogólnych, cała odpowiedź da nam błąd kompilacji. Możesz użyć return default(T);. Próbka poniżej, aby wyjaśnić dalej.

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
źródło
Dlaczego głosowanie negatywne?
Karthikeyan VK
Pytanie nie dotyczyło metod asynchronicznych :)
Frode Nilsen
0
return await Task.FromResult(new MyClass());
JMH
źródło
3
Chociaż ten kod może rozwiązać pytanie, w tym wyjaśnienie, w jaki sposób i dlaczego to rozwiązuje problem, naprawdę pomógłby poprawić jakość twojego postu i prawdopodobnie doprowadziłby do większej liczby głosów. Pamiętaj, że odpowiadasz na pytanie dla czytelników w przyszłości, nie tylko dla osoby zadającej teraz pytanie. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnień i dać wskazówkę co zastosować ograniczenia i założenia.
David Buck