Wednesday, January 21, 2015

Async и await в C# 6.0

Сегодня мы рассмотрим тему async/await для C# 6, а именно – тот нюанс, что ключевое слово await теперь работает в блоках catch/finally. Не знаю, как вам, а мне очень интересно, как же это все реализовано изнутри IL кода. Как Microsoft решил ту или иную возможность в языке C#. Если вы знаете, как работают таски с использованием ключевых слов async/await изнутри, и такие слова, как TaskAwaiter, не вызывают у вас недопонимания, тогда добро пожаловать в углубленный анализ. Если же вам неизвестно, как кухня async и await работает изнутри, то вам, наверное, следует почитать об этом, например, здесь: "Тонкости использования async и await".
Давайте рассмотрим пример с использованием приведенных выше ключевых слов для C# 5.
class Program
{
    static void Main(string[] args)
    {
        TestAsyncMethod();
        Console.ReadLine();
    }

    public static async void TestAsyncMethod()
    {
        try
        {
            await Task.FromResult(5);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Catch Block");
        }
        finally
        {
            Console.WriteLine("Finally Block Block");
        }
    }
}
Приведенный выше код не позволяет обрабатывать асинхронно код в блоке catch или finally. Например, следующие изменения не скомпилируются.
Все дело в том, что язык C# 5.0 не поддерживает такой синтаксис. Теперь немного теории о том, что происходит внутри IL кода, если данный код скомпилировать.
У нас будет сгенерирован класс <TestAsyncMethod> d_0
В этом классе строится машина состояний, благодаря которой наш код работает с ключевым словом await. К сожалению, IL Spy генерирует такой код, который сложно читать, но если посмотреть на этот код с помощью .NET Reflector, сразу все станет на свои места.
private struct <TestAsyncMethod>d__0 : IAsyncStateMachine
{
    // Fields
    public int <>1__state;
    public AsyncVoidMethodBuilder <>t__builder;
    private object <>t__stack;
    private TaskAwaiter<int> <>u__$awaiter1;

    // Methods
    private void MoveNext()
    {
        try
        {
            bool flag = true;
            switch (this.<>1__state)
            {
                case -3:
                    goto Label_00F4;
            }
            try
            {
                int num = this.<>1__state;
                if (num == 0)
                {
                }
                try
                {
                    TaskAwaiter<int> awaiter;
                    num = this.<>1__state;
                    if (num != 0)
                    {
                        awaiter = Task.FromResult<int>(5).GetAwaiter();
                        if (!awaiter.IsCompleted)
                        {
                            this.<>1__state = 0;
                            this.<>u__$awaiter1 = awaiter;
                            this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, Program.<TestAsyncMethod>d__0>(ref awaiter, ref this);
                            flag = false;
                            return;
                        }
                    }
                    else
                    {
                        awaiter = this.<>u__$awaiter1;
                        this.<>u__$awaiter1 = new TaskAwaiter<int>();
                        this.<>1__state = -1;
                    }
                    awaiter.GetResult();
                    awaiter = new TaskAwaiter<int>();
                }
                catch (Exception)
                {
                    Console.WriteLine("Catch Block");
                }
            }
            finally
            {
                if (flag)
                {
                    Console.WriteLine("Finally Block Block");
                }
            }
        }
        catch (Exception exception2)
        {
            this.<>1__state = -2;
            this.<>t__builder.SetException(exception2);
            return;
        }
    Label_00F4:
        this.<>1__state = -2;
        this.<>t__builder.SetResult();
    }

    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine param0)
    {
        this.<>t__builder.SetStateMachine(param0);
    }
}
Как видите, код нестрашный и вполне читабельный. Если вы внимательно на него посмотрите, то увидите, что компилятор сгенерировал обычный switch и переход между состояниями машины с помощью goto. Если переписать выше код C#, который лучше читаемый для человеческого глаза, то мы увидим, что все не так уж страшно выглядит.
Переписанный пример в читабельный вид:
public static void TestAsyncMethod()
{
    var builder = new AsyncVoidMethodBuilder();
    int state = 0;
    Action moveNext = null;
    TaskAwaiter<int> awaiter = new TaskAwaiter<int>();

    moveNext = () =>
    {
        try
        {
            bool flag = true;
            switch (state)
            {
                case -3:
                    goto Label_00F4;
            }
            try
            {
                int num = state;
                if (num == 0)
                {
                }
                try
                {
                    TaskAwaiter<int> aw;
                    num = state;
                    if (num != 0)
                    {
                        aw = Task.FromResult<int>(5).GetAwaiter();
                        if (!awaiter.IsCompleted)
                        {
                            state = 0;
                            awaiter = aw;
                            awaiter.OnCompleted(moveNext);
                            flag = false;
                            return;
                        }
                    }
                    else
                    {
                        aw = awaiter;
                        awaiter = new TaskAwaiter<int>();
                        state = -1;
                    }
                    aw.GetResult();
                    aw = new TaskAwaiter<int>();
                }
                catch (Exception)
                {
                    Console.WriteLine("Catch Block");
                }
            }
            finally
            {
                if (flag)
                {
                    Console.WriteLine("Finally Block Block");
                }
            }
        }
        catch (Exception exception2)
        {
            state = -2;
            builder.SetException(exception2);
            return;
        }
    Label_00F4:
        state = -2;
        builder.SetResult();
    };
    moveNext();
}
А теперь посмотрим пример на C 6.0 и разберем, что изменилось в новой версии языка.
class Program
{
    static void Main(string[] args)
    {
        TestCatchMachodWithAsync();
        ReadLine();
    }

    private static async void TestCatchMachodWithAsync()
    {
        try
        {
            WriteLine(await Task.Factory.StartNew(() =>
            {
                int res = 0;
                for(int i = 0; i < 100; i++)
                {
                    res += i;
                    if(i == 20)
                        throw new Exception("Hello World");
                }

                return res;
            }));
        }
        catch (Exception ex)
        {
            WriteLine(await Task.FromResult("Catch block"));
        }
        finally
        {
            WriteLine(await Task.FromResult("Finally block"));
        }
    }
}
Этот пример работает в C# 6.0. Давайте посмотрим, какой IL код был сгенерирован. Для этого скомпилируем проект в Visual Studio 2015 Preview, а затем откроем наш экзешник в .Net Reflector.
[CompilerGenerated]
private sealed class <TestCatchMachodWithAsync>d__1 : IAsyncStateMachine
{
    // Fields
    public int <>1__state;
    public object <>7__wrap1;
    public int <>7__wrap2;
    public object <>7__wrap3;
    public int <>7__wrap4;
    public AsyncVoidMethodBuilder <>t__builder;
    public TaskAwaiter<int> <>u__$awaiter3;
    public TaskAwaiter<string> <>u__$awaiter4;

    // Methods
    public <TestCatchMachodWithAsync>d__1();
    private void MoveNext();
    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine);
}
Теперь раскроем полностью весь код и посмотрим, что изменилось.
[CompilerGenerated]
private sealed class <TestCatchMachodWithAsync>d__1 : IAsyncStateMachine
{
    // Fields
    public int <>1__state;
    public object <>7__wrap1;
    public int <>7__wrap2;
    public object <>7__wrap3;
    public int <>7__wrap4;
    public AsyncVoidMethodBuilder <>t__builder;
    public TaskAwaiter<int> <>u__$awaiter3;
    public TaskAwaiter<string> <>u__$awaiter4;

    // Methods
    private void MoveNext()
    {
        Exception exception3;
        int num = this.<>1__state;
        try
        {
            Program.<TestCatchMachodWithAsync>d__1 d__;
            TaskAwaiter<string> awaiter2;
            string str2;
            object obj2;
            switch (num)
            {
                case 0:
                case 1:
                    break;

                case 2:
                    goto Label_01F6;

                default:
                    this.<>7__wrap1 = null;
                    this.<>7__wrap2 = 0;
                    break;
            }
            try
            {
                int result;
                switch (num)
                {
                    case 0:
                        break;

                    case 1:
                        goto Label_0160;

                    default:
                        this.<>7__wrap4 = 0;
                        break;
                }
                try
                {
                    TaskAwaiter<int> awaiter;
                    if (num != 0)
                    {
                        awaiter = Task.Factory.StartNew<int>(Program.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 ?? (Program.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 = new Func<int>(Program.<>c__DisplayClass0.CS$<>9__inst.<TestCatchMachodWithAsync>b__1))).GetAwaiter();
                        if (!awaiter.IsCompleted)
                        {
                            this.<>1__state = num = 0;
                            this.<>u__$awaiter3 = awaiter;
                            d__ = this;
                            this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, Program.<TestCatchMachodWithAsync>d__1>(ref awaiter, ref d__);
                            return;
                        }
                    }
                    else
                    {
                        awaiter = this.<>u__$awaiter3;
                        this.<>u__$awaiter3 = new TaskAwaiter<int>();
                        this.<>1__state = num = -1;
                    }
                    result = awaiter.GetResult();
                    awaiter = new TaskAwaiter<int>();
                    int num2 = result;
                    Console.WriteLine(num2);
                }
                catch (Exception exception)
                {
                    this.<>7__wrap3 = exception;
                    this.<>7__wrap4 = 1;
                }
                result = this.<>7__wrap4;
                if (result != 1)
                {
                    goto Label_019D;
                }
                Exception exception2 = (Exception) this.<>7__wrap3;
                awaiter2 = Task.FromResult<string>("Catch block").GetAwaiter();
                if (awaiter2.IsCompleted)
                {
                    goto Label_017D;
                }
                this.<>1__state = num = 1;
                this.<>u__$awaiter4 = awaiter2;
                d__ = this;
                this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Program.<TestCatchMachodWithAsync>d__1>(ref awaiter2, ref d__);
                return;
            Label_0160:
                awaiter2 = this.<>u__$awaiter4;
                this.<>u__$awaiter4 = new TaskAwaiter<string>();
                this.<>1__state = num = -1;
            Label_017D:
                str2 = awaiter2.GetResult();
                awaiter2 = new TaskAwaiter<string>();
                string str = str2;
                Console.WriteLine(str);
            Label_019D:
                this.<>7__wrap3 = null;
            }
            catch (object obj1)
            {
                obj2 = obj1;
                this.<>7__wrap1 = obj2;
            }
            awaiter2 = Task.FromResult<string>("Finally block").GetAwaiter();
            if (awaiter2.IsCompleted)
            {
                goto Label_0213;
            }
            this.<>1__state = num = 2;
            this.<>u__$awaiter4 = awaiter2;
            d__ = this;
            this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Program.<TestCatchMachodWithAsync>d__1>(ref awaiter2, ref d__);
            return;
        Label_01F6:
            awaiter2 = this.<>u__$awaiter4;
            this.<>u__$awaiter4 = new TaskAwaiter<string>();
            this.<>1__state = num = -1;
        Label_0213:
            str2 = awaiter2.GetResult();
            awaiter2 = new TaskAwaiter<string>();
            string str3 = str2;
            Console.WriteLine(str3);
            obj2 = this.<>7__wrap1;
            if (obj2 != null)
            {
                exception3 = obj2 as Exception;
                if (exception3 == null)
                {
                    throw obj2;
                }
                ExceptionDispatchInfo.Capture(exception3).Throw();
            }
            int num1 = this.<>7__wrap2;
            this.<>7__wrap1 = null;
        }
        catch (Exception exception4)
        {
            exception3 = exception4;
            this.<>1__state = -2;
            this.<>t__builder.SetException(exception3);
            return;
        }
        this.<>1__state = -2;
        this.<>t__builder.SetResult();
    }

    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
}
Как видим из примера, у нас изменения коснулись в основном переключения контекста, и стало больше меток goto. Попробуем переписать этот пример так, чтобы его можно было скомпилировать. Сначала с небольшими модификациями, а затем приведем его в полностью читабельный вид. Интересно, что для теста я компилировал и запускал на C# 5.0.
internal class Program
{
    private static void Main(string[] args)
    {
        TestNewAsync();
        Console.ReadLine();
    }

    private static void TestNewAsync()
    {
        int state = 0;
        object wrap1= null;
        int wrap2 = 0;
        object wrap3 = null;
        int wrap4 = 0;
        var builder = new AsyncVoidMethodBuilder();
        var awaiter3 = new TaskAwaiter<int>();
        var awaiter4 = new TaskAwaiter<string>();
        Action moveNext = null;

        moveNext = () =>
        {
            Exception exception3;
            int num = state;
            try
            {
                TaskAwaiter<string> awaiter2;
                string str2;
                object obj2;
                switch (num)
                {
                    case 0:
                    case 1:
                        break;

                    case 2:
                        goto Label_01F6;

                    default:
                        wrap1 = null;
                        wrap2 = 0;
                        break;
                }
                try
                {
                    int result;
                    switch (num)
                    {
                        case 0:
                            break;

                        case 1:
                            goto Label_0160;

                        default:
                            wrap4 = 0;
                            break;
                    }
                    try
                    {
                        TaskAwaiter<int> awaiter;
                        if (num != 0)
                        {
                            awaiter = Task.Factory.StartNew<int>(GetResult).GetAwaiter();
                            if (!awaiter.IsCompleted)
                            {
                                state = num = 0;
                                awaiter3 = awaiter;
                                awaiter3.UnsafeOnCompleted(moveNext);
                                return;
                            }
                        }
                        else
                        {
                            awaiter = awaiter3;
                            awaiter3 = new TaskAwaiter<int>();
                            state = num = -1;
                        }
                        result = awaiter.GetResult();
                        awaiter = new TaskAwaiter<int>();
                        int num2 = result;
                        Console.WriteLine(num2);
                    }
                    catch (Exception exception)
                    {
                        wrap3 = exception;
                        wrap4 = 1;
                    }
                    result = wrap4;
                    if (result != 1)
                    {
                        goto Label_019D;
                    }
                    Exception exception2 = (Exception) wrap3;
                    awaiter2 = Task.FromResult<string>("Catch block").GetAwaiter();
                    if (awaiter2.IsCompleted)
                    {
                        goto Label_017D;
                    }
                    state = num = 1;
                    awaiter4 = awaiter2;
                    awaiter4.UnsafeOnCompleted(moveNext);
                    return;
                    Label_0160:
                    awaiter2 = awaiter4;
                    awaiter4 = new TaskAwaiter<string>();
                    state = num = -1;
                    Label_017D:
                    str2 = awaiter2.GetResult();
                    awaiter2 = new TaskAwaiter<string>();
                    string str = str2;
                    Console.WriteLine(str);
                    Label_019D:
                    wrap3 = null;
                }
                catch (Exception obj1)
                {
                    obj2 = obj1;
                    wrap1 = obj2;
                }
                awaiter2 = Task.FromResult<string>("Finally block").GetAwaiter();
                if (awaiter2.IsCompleted)
                {
                    goto Label_0213;
                }
                state = num = 2;
                awaiter4 = awaiter2;
                awaiter4.UnsafeOnCompleted(moveNext);
                return;
                Label_01F6:
                awaiter2 = awaiter4;
                awaiter4 = new TaskAwaiter<string>();
                state = num = -1;
                Label_0213:
                str2 = awaiter2.GetResult();
                awaiter2 = new TaskAwaiter<string>();
                string str3 = str2;
                Console.WriteLine(str3);
                obj2 = wrap1;
                if (obj2 != null)
                {
                    exception3 = obj2 as Exception;
                    if (exception3 == null)
                    {
                        throw new Exception();
                    }
                    ExceptionDispatchInfo.Capture(exception3).Throw();
                }
                int num1 = wrap2;
                wrap1 = null;
            }
            catch (Exception exception4)
            {
                exception3 = exception4;
                state = -2;
                builder.SetException(exception3);
                return;
            }
            state = -2;
            builder.SetResult();
        };

        moveNext();
    }

    public static int GetResult()
    {
        int res = 0;
        for(int i = 0; i < 100; i++)
        {
            res += i;
            if(i == 20)
                throw new Exception("Hello World");
        }

        return res;
    }
}
Читать такой код по-прежнему сложно, потому что продумать нормальные имена, вместо генерируемых компилятором, не так уж просто.  Но сейчас вы можете скопировать этот код и запустить в вашей студии.
В данном коде можно увидеть пример использования класса ExceptionDispatchInfo c 4.5 фреймворка, который представляет исключение, состояние которого захватывается в некоторой точке кода ссылка. Давайте разберем то, что здесь происходит. Для того чтобы определить, какой сейчас блок должен выполняться, используется два блока switch. Первый блок:
switch (num)
{
    case 0:
    case 1:
        break;

    case 2:
        goto Label_01F6;

    default:
        wrap1 = null;
        wrap2 = 0;
        break;
}
Если у нас state == 2, значит, мы переходим в блок finally. Второй switch используется для перехода в блок catch.
switch (num)
{
    case 0:
        break;

    case 1:
        goto Label_0160;

    default:
        wrap4 = 0;
        break;
}
Вот как это выглядит в реальном коде:
switch (num)
{
    case 0:
    case 1:
        break;

    case 2:
        goto Label_01F6;

    default:
        wrap1 = null;
        wrap2 = 0;
        break;
}
try
{
    int result;
    switch (num)
    {
        case 0:
            break;

        case 1:
            goto Label_0160;

        default:
            wrap4 = 0;
            break;
    }
Я долго думал, как можно переделать такой подход с оператором goto, на подход без goto, и мне пришла только идея с установкой какого-то состояния и постоянно вызывать циклически самого себя. Схематически это показано на примере ниже.
private static void TestAsync()
{
    int state = 0;

    Action moveNext = null;

    moveNext = () =>
    {
        int num = state;
        switch (num)
        {
            case 1: //Main Block
            {
                Console.WriteLine("Main block");
                state = 3;
                moveNext();
                break;
            }
            case 3: // Catch block
            {
                state = 4;
                Console.WriteLine("Catch Block");
                moveNext();
                break;
            }
            case 4: // Finally block
            {
                Console.WriteLine("Finally block");
                break;
            }
            default:
            {
                state = 1;
                moveNext();
                break;
            }
        }
    };

    moveNext();
}
Но к сожалению, если вы перепишете код так, то он не станет читабельнее. Я переписал данный код через один switch. Мне пришлось добавить свои состояния для перехода. Если вы хотите писать такой код, то вам нужно написать свою машину состояний. Это есть ничто иное, как паттерн State. Посмотрите на этот пример, он напоминает чем-то своим подходом то, что мы реализуем сейчас. То есть, если хорошо продумать такой переход состояний, то можно в коде обойтись без ключевого слова await. Последнее предложение было шуткой. Мне бы лично не хотелось писать постоянно такой код. Мало того, что его сложно писать, так кроме того, его не так-то просто понять и сопровождать. А так как мы привыкли, что всю черновую работу за нас делает компилятор, то проделывать эту работу самим за него не очень хорошо. Продолжим наш разбор кода.
Начнем, пожалуй, с переменных, которые мы объявили.
int state = 0;
object wrap1= null;
int wrap2 = 0;
object wrap3 = null;
int wrap4 = 0;
var builder = new AsyncVoidMethodBuilder();
var awaiter3 = new TaskAwaiter<int>();
var awaiter4 = new TaskAwaiter<string>();
Action moveNext = null;
Как мы уж разобрали, что переменная state – это хранение состояния машины. Следующие переменные wrap1 и wrap3 используются для хранения ошибок на разных этапах выполнения функции, а переменные wrap2/wrap4 используются для того, чтобы идентифицировать, успешно мы получили результат выполнения или нет. Это что-то вроде вспомогательных состояний для определения успешности получения результата с помощью функции GetResult класса TaskAwaiter. Давайте посмотрим кусок кода, чтобы понять, как используются эти переменные.  
try
{
    TaskAwaiter<int> awaiter;
    if (num != 0)
    {
        awaiter = Task.Factory.StartNew<int>(GetResult).GetAwaiter();
        if (!awaiter.IsCompleted)
        {
            state = num = 0;
            awaiter3 = awaiter;
            awaiter3.UnsafeOnCompleted(moveNext);
            return;
        }
    }
    else
    {
        awaiter = awaiter3;
        awaiter3 = new TaskAwaiter<int>();
        state = num = -1;
    }
    result = awaiter.GetResult();
    awaiter = new TaskAwaiter<int>();
    int num2 = result;
    Console.WriteLine(num2);
}
catch (Exception exception)
{
    wrap3 = exception;
    wrap4 = 1;
}
result = wrap4;
if (result != 1)
{
    goto Label_019D;
}
Exception exception2 = (Exception) wrap3;
awaiter2 = Task.FromResult<string>("Catch block").GetAwaiter();
if (awaiter2.IsCompleted)
{
    goto Label_017D;
}
У нас приведен кусок кода, в котором мы ожидаем завершения результата основного блока кода (тот, который у нас внутри try). В данном примере стоит обратить внимание на блок catch, в котором мы сохраняем ошибку, а также ставим флаг wrap4 в 1.
catch (Exception exception)
{
    wrap3 = exception;
    wrap4 = 1;
}
Это делается для того, чтобы на следующем ходу определить результат выполнения предыдущей операции.
result = wrap4;
if (result != 1)
{
    goto Label_019D;
}
Exception exception2 = (Exception) wrap3;
awaiter2 = Task.FromResult<string>("Catch block").GetAwaiter();
if (awaiter2.IsCompleted)
{
    goto Label_017D;
}
По умолчанию у нас, как вы помните, wrap4 равен нулю – начальная инициализация, и он будет равен 1 только в том случае, если у нас произошла ошибка. Если результат не равен нулю, то мы просто обнуляем переменную wrap3, в которую сохраняем ошибку, и переходим в блок finally. Если же у нас произошла все-таки ошибка, то мы получим ее в блоке catch и запустим ожидание завершения awaiter2 для данного блока catch. Интересно, что если посмотреть на эту структуру со стороны, то можно увидеть, что данный код имеет следующую структуру:
try // Try Base block
{
    try // Try finally block
    {
        try // Try catch block
        {

        }
        catch // Catch catch block
        {
                       
        }
    }
    catch // Catch finally catch block
    {

    }
}
catch //catch Base block
{
               
}
Ну вот в такую структуру и развернут наш код, который сгенерировал нам компилятор языка C# 6.0.
У нас есть признак того, успешно мы завершили блок try или нет (метка wrap 4). Если успешно, переходим сразу в конец блока catch (в приведенном схематическом примере выше это блок с меткой “Catch catch block”), так как за ним начнется выполнение автоматом блока finally.
}
catch (Exception obj1)
{
    obj2 = obj1;
    wrap1 = obj2;
}
awaiter2 = Task.FromResult<string>("Finally block").GetAwaiter();
if (awaiter2.IsCompleted)
{
    goto Label_0213;
}
state = num = 2;
awaiter4 = awaiter2;
awaiter4.UnsafeOnCompleted(moveNext);
Этот кусок блока отвечает за блок finally в нашем исходном примере. Только в этом блоке, когда мы закончили нашу обработку, мы проверяем, не произошло ли у нас каких-либо ошибок.
str2 = awaiter2.GetResult();
awaiter2 = new TaskAwaiter<string>();
string str3 = str2;
Console.WriteLine(str3);
obj2 = wrap1;
if (obj2 != null)
{
    exception3 = obj2 as Exception;
    if (exception3 == null)
    {
        throw new Exception();
    }
    ExceptionDispatchInfo.Capture(exception3).Throw();
}
int num1 = wrap2;
wrap1 = null;
Если такие ошибки есть, то мы прокидываем эти ошибки дальше. Мне кажется, что .NET Reflector как-то неверно сгенерировал код для этой части, потому что вместо данного кода
if (obj2 != null)
{
    exception3 = obj2 as Exception;
    if (exception3 == null)
    {
        throw new Exception();
    }
    ExceptionDispatchInfo.Capture(exception3).Throw();
}
Изначальный код был вот такой:
if (obj2 != null)
{
    exception3 = obj2 as Exception;
    if (exception3 == null)
    {
        throw obj2;
    }
    ExceptionDispatchInfo.Capture(exception3).Throw();
}
Если нам модифицировать наш код с использованием вместо object типа Exception, тогда все стает на свои места. Так что если вы захотите все-таки привести этот код к читаемому виду и эмуляцией работы try/catch/finally блока с ключевым словом await, то вас ждет рутинная, но, как мне кажется, интересная работа (если вам интересно постоянно докапываться до чего-то нового). Если мы вместо void будем возвращать Task, то ваш код вырастет почти в два раза, так как с таском немного сложнее в том плане, что нам уже нужно устанавливать конкретный результат, который для нашего AsyncVoidMethodBuilder был пустым.
state = -2;
builder.SetResult();
А так нам может понадобиться установить результат на одном из этапов выполнения нашего кода.

На этом буду заканчивать статью по async/await в C# 6.0. Как видим, особых изменений не случилось, только расширилось построение IL кода, для того чтобы генерировать актуальную машину состояний. Следующую статью по C# 6.0 я посвящу фильтрам исключений. Уже есть замечательная статья по данной теме от Сергея Теплякова "Фильтры исключений в C# 6.0", ну а я постараюсь осветить свое виденье данной темы и покопаюсь немного в IL коде, разбирая подноготную фильтров исключений в 6-м шарпе. Надеюсь, это будет не очень скучно, потому что я постараюсь высветлить фильтры ошибок с более низкого уровня, чем это описано у Теплякова. Получится у меня или нет – как говорят, поживем-увидим. 

No comments:

Post a Comment