Асинхронни фукции чрез Task- кой подход е по-ефективен?

+1 глас
108 прегледа
попитан 2016 юли 1 от Nikoleta.V. (4,090 точки)

Чудех се кой подход ще е по-ефективен от гледна точка на памет и използване на ресурси като цяло. 

Относно подход #1. Трудно ми е да си представя как Task обектите се създават и нишките тръгват? Може ли някой да ми обясни в детайли какво се случва в дълбочина? 

Подход #2. Доколкото знам, компилаторът генерира state machine и използва yield return. #1 изглежда рекурсивен по концепция, но дали ще е така при един stack-frame? 

Подход #1: 

internal static Task ExecuteAsyncWithRetry(Func<Task> methodToExecute, Func<bool> shouldRetry) 

    { 

        var tcs = new TaskCompletionSource<object>(); 

        try 

        { 

            return methodToExecute().ContinueWith<Task>((t) => 

            { 

                if (t.IsFaulted || t.IsCanceled) 

                { 

                    if (shouldRetry()) 

                    { 

                        return ExecuteAsyncWithRetry(methodToExecute, shouldRetry); 

                    } 

                    else 

                    { 

                        tcs.SetException(t.Exception); 

                    } 

                } 

                else 

                { 

                    tcs.SetResult(null); 

                } 

                return tcs.Task; 

            }, TaskContinuationOptions.ExecuteSynchronously).Unwrap(); 

        } 

        catch(Exception ex) 

        { 

            tcs.SetException(ex); 

        } 

        return tcs.Task; 

    } 

Подход #2 

internal static async Task ExecuteWithRetry(Func<Task> methodToExecute, Func<bool> shouldRetry) 

    { 

        while (true) 

        { 

            try 

            { 

                await methodToExecute(); 

            } 

            catch(Exception ex) 

            { 

                if(!shouldRetry()) 

                { 

                    throw; 

                } 

            } 

        } 

    } 

1 отговор

0 гласа
отговорени 2016 юли 5 от valeri.hristov (7,340 точки)

В първия случай, продължението върви на същата нишка, на която се завършва задачата, заради TaskContinuationOptions.ExecuteSynchronously.

Във втория случай, то ще върви в оригиналния synchronization context(ако methodToExecute се извика на нишка със synchronization context)

Въпреки че първият подход може да е по-ефективен, той може и да е по-труден за разбиране.

Следвайки KISS принципа с малко изменение:

await methodToExecute().ConfigureAwait(false);

#1 изглежда рекурсивен по концепция, но дали ще е така при един stack-frame?

Дали ще се случи рекурсивно в stack-frame-а или асинхронно в друг stack-frame, зависи изцяло от това какво се случва в methodToExecute. В повечето случаи няма рекурсия ако използваш асинхронно апи в метода. Например HttpClient.GetStringAsync завършва на случайна IOCP нишка от thread pool-а.

Но, дори асинхронно апи може да завърши синхронно на същата нишка (MemoryStream.ReadAsync или Task.Delay(0)). В такъв случай има рекурсия.

Използването на TaskCompletionSource.SetResult в methodToExecute може да задейства синхронни операции.

Ако искаш да избегнеш възможността за рекурси, извиквай methodToExecute чрез Task.Run. Или още по-добре, премахни TaskContinuationOptions.ExecuteSynchronously.

...