В этой статье мы поговорим об очень интересной теме, с которой у многих разработчиков возникают трудности в понимании. Часть разработчиков не понимает
эту тему из-за специфики своей работы и так как не стыкались с ней, другие просто не задавались вопросом, почему происходит так или иначе, и как вообще это работает.
Сегодня мы обсудим использование новых ключевых слов, которые появились в .Net Framework
4.5 – async/await. Эти ключевые слова добавлены для упрощения
асинхронного программирования. Они скрывают некоторые сложности, которые мы
можем упустить из виду, что, соответственно, принесет в наш код дополнительные
проблемы. С помощью ключевых слов async/await создавать асинхронные вызовы становится так же легко, как
и синхронные. Давайте создадим тестовый пример AsynchronousSample, в котором
проемонстрируем наши примеры.
Не забудьте при создании выбрать .Net Framework 4.5, так как async/await
появились именно там.
class Program
{
static void Main(string[] args)
{
Action a = () =>
{
var task = GetTaskResult();
task.Wait();
Console.WriteLine("Get Result {0}", task.Result);
};
a();
Console.ReadLine();
}
public static Task<long> GetTaskResult()
{
var task = Task<long>.Factory.StartNew(() =>
{
long counter = 0;
for (int i = 0; i < 10000000; i++)
{
DateTime.Now.ToString(); //special code calculate this counter more slowly
counter += i;
}
return counter;
});
return task;
}
}
Выше я написал простенький пример, который высчитывает сумму последовательности
чисел в 10М. Для того чтобы дождаться выполнения этого кода, мы вызываем метод,
task.Wait();
который
говорит нашему коду ожидать завершение таска, для того чтобы можно было бы
получить результат. Если мы будем запускать такой код в UI-приложении, написанном, например, на WPF и WindowsForms, то указанный код приведет к блокировке потока, результат которого мы ожидаем. Естественно это отразится на основном потоке,
так как ожидание завершения запущенного таска мы ожидаем в главном потоке. А
теперь представьте ситуацию, что нам нужно выполнить несколько таких методов
один за другим?
static void Main(string[] args)
{
Action a = () =>
{
var task = GetTaskResult();
task.Wait();
Console.WriteLine("Get Result {0}", task.Result);
var task2 = GetTaskResult();
task2.Wait();
Console.WriteLine("Get Result
Task2 {0}", task2.Result);
var task3 = GetTaskResult();
task3.Wait();
Console.WriteLine("Get Result
Task 3 {0}", task3.Result);
};
a();
Console.ReadLine();
}
Код и сам по себе читается непросто, не говоря уже о выполнении. Теперь немного усложним задачу. Добавим класс Data, в котором будем хранить результат подсчета нашей
последовательности, и будем возвращать этот метод сразу, как получим результат
от метода GetTaskResult.
public class Data
{
public long Result { get; set; }
}
Для этого мы можем воспользоваться способом с Wait,
но воспользуемся вторым методом, который позволит сделать, по сути, то же самое – это
метод ContinueWith. Давайте посмотрим на то, как изменится наш код в данном
случае.
static void Main(string[] args)
{
Action a = () =>
{
var task = GetData();
task.Wait();
Console.WriteLine("Get Result {0}", task.Result.Result);
};
a();
Console.ReadLine();
}
public static Task<Data> GetData()
{
return GetTaskResult().ContinueWith(t => new Data { Result = t.Result });
}
public static Task<long> GetTaskResult()
{
var task = Task<long>.Factory.StartNew(() =>
{
long counter = 0;
for (int i = 0; i < 10000000; i++)
{
DateTime.Now.ToString(); //special code calculate
this counter more slowly
counter += i;
}
return counter;
});
return task;
}
Основная сточка кода, которая возвращает нужный результат, – это метод GetData.
public static Task<Data> GetData()
{
return GetTaskResult().ContinueWith(t => new Data { Result = t.Result });
}
Прежде чем перейти к теории, перепишу код с использованием
ключевых слов async/await, а затем расскажу о ключевых отличиях.
static void Main(string[] args)
{
Action a = async () =>
{
var data = await GetData();
Console.WriteLine("Get Result {0}", data.Result);
};
a();
Console.ReadLine();
}
public static async Task<Data> GetData()
{
var result = await GetTaskResult();
return new Data { Result = result };
}
Метод GetTaskResult остается без
изменений. Изменения затронули мой Action a, а также метод GetData. Что этот метод,
что предыдущей имеет накладные расходы. В этих двух подходах есть существенное
отличие.
- Task строит цепочку продолжений, которая увеличивается в соответствии с количеством задач, связанных последовательно, и состояние системы управляется через замыкания, найденные компилятором.
- Async/await строит машину состояний, которая не использует дополнительных ресурсов при добавлении новых шагов. Однако компилятор может определить больше переменных для сохранение в стеки машины состояний, в зависимости от вашего кода (и компилятора).
Давайте посмотрим, как же выглядит это на уровне IL-кода. Первым делом мы посмотрим, какой код у нас сгенерирует
компилятор языка C# для кода без ключевых слов async/await.
.method public hidebysig static
class [mscorlib]System.Threading.Tasks.Task`1<int64> GetTaskResult () cil managed
{
// Method begins at RVA 0x2170
// Code size 49 (0x31)
.maxstack 3
.locals init (
[0] class [mscorlib]System.Threading.Tasks.Task`1<int64> task,
[1] class [mscorlib]System.Threading.Tasks.Task`1<int64> CS$1$0000
)
IL_0000: nop
IL_0001: call class [mscorlib]System.Threading.Tasks.TaskFactory`1<!0> class [mscorlib]System.Threading.Tasks.Task`1<int64>::get_Factory()
IL_0006: ldsfld class [mscorlib]System.Func`1<int64> AsynchronousSample.Program::'CS$<>9__CachedAnonymousMethodDelegate6'
IL_000b: brtrue.s IL_0020
IL_000d: ldnull
IL_000e: ldftn int64 AsynchronousSample.Program::'<GetTaskResult>b__5'()
IL_0014: newobj instance void class [mscorlib]System.Func`1<int64>::.ctor(object, native int)
IL_0019: stsfld class [mscorlib]System.Func`1<int64> AsynchronousSample.Program::'CS$<>9__CachedAnonymousMethodDelegate6'
IL_001e: br.s IL_0020
IL_0020: ldsfld class [mscorlib]System.Func`1<int64> AsynchronousSample.Program::'CS$<>9__CachedAnonymousMethodDelegate6'
IL_0025: callvirt instance class [mscorlib]System.Threading.Tasks.Task`1<!0> class [mscorlib]System.Threading.Tasks.TaskFactory`1<int64>::StartNew(class [mscorlib]System.Func`1<!0>)
IL_002a: stloc.0
IL_002b: ldloc.0
IL_002c: stloc.1
IL_002d: br.s IL_002f
IL_002f: ldloc.1
IL_0030: ret
} // end of method Program::GetTaskResult
class [mscorlib]System.Threading.Tasks.Task`1<int64> GetTaskResult () cil managed
{
// Method begins at RVA 0x2170
// Code size 49 (0x31)
.maxstack 3
.locals init (
[0] class [mscorlib]System.Threading.Tasks.Task`1<int64> task,
[1] class [mscorlib]System.Threading.Tasks.Task`1<int64> CS$1$0000
)
IL_0000: nop
IL_0001: call class [mscorlib]System.Threading.Tasks.TaskFactory`1<!0> class [mscorlib]System.Threading.Tasks.Task`1<int64>::get_Factory()
IL_0006: ldsfld class [mscorlib]System.Func`1<int64> AsynchronousSample.Program::'CS$<>9__CachedAnonymousMethodDelegate6'
IL_000b: brtrue.s IL_0020
IL_000d: ldnull
IL_000e: ldftn int64 AsynchronousSample.Program::'<GetTaskResult>b__5'()
IL_0014: newobj instance void class [mscorlib]System.Func`1<int64>::.ctor(object, native int)
IL_0019: stsfld class [mscorlib]System.Func`1<int64> AsynchronousSample.Program::'CS$<>9__CachedAnonymousMethodDelegate6'
IL_001e: br.s IL_0020
IL_0020: ldsfld class [mscorlib]System.Func`1<int64> AsynchronousSample.Program::'CS$<>9__CachedAnonymousMethodDelegate6'
IL_0025: callvirt instance class [mscorlib]System.Threading.Tasks.Task`1<!0> class [mscorlib]System.Threading.Tasks.TaskFactory`1<int64>::StartNew(class [mscorlib]System.Func`1<!0>)
IL_002a: stloc.0
IL_002b: ldloc.0
IL_002c: stloc.1
IL_002d: br.s IL_002f
IL_002f: ldloc.1
IL_0030: ret
} // end of method Program::GetTaskResult
В коде нет ничего такого, что могло бы вызвать страх. Код очень подобный
тому, который у нас в языке C#, за исключением CachedAnonymousMethodDelegate, которым в IL-коде обозначается создание анонимного метода.
Теперь посмотрим тот же код, который у нас будет сгенерирован с ключевыми
словами async/await.
.method public hidebysig static
class [mscorlib]System.Threading.Tasks.Task`1<class AsynchronousSample.Data> GetData () cil managed
{
.custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = (
01 00 28 41 73 79 6e 63 68 72 6f 6e 6f 75 73 53
61 6d 70 6c 65 2e 50 72 6f 67 72 61 6d 2b 3c 47
65 74 44 61 74 61 3e 64 5f 5f 37 00 00
)
// Method begins at RVA 0x22dc
// Code size 54 (0x36)
.maxstack 2
.locals init (
[0] valuetype AsynchronousSample.Program/'<GetData>d__7',
[1] class [mscorlib]System.Threading.Tasks.Task`1<class AsynchronousSample.Data>,
[2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>
)
IL_0000: ldloca.s 0
IL_0002: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::Create()
IL_0007: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_000c: ldloca.s 0
IL_000e: ldc.i4.m1
IL_000f: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_0014: ldloca.s 0
IL_0016: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_001b: stloc.2
IL_001c: ldloca.s 2
IL_001e: ldloca.s 0
IL_0020: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::Start<valuetype AsynchronousSample.Program/'<GetData>d__7'>(!!0&)
IL_0025: ldloca.s 0
IL_0027: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_002c: call instance class [mscorlib]System.Threading.Tasks.Task`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::get_Task()
IL_0031: stloc.1
IL_0032: br.s IL_0034
IL_0034: ldloc.1
IL_0035: ret
} // end of method Program::GetData
class [mscorlib]System.Threading.Tasks.Task`1<class AsynchronousSample.Data> GetData () cil managed
{
.custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = (
01 00 28 41 73 79 6e 63 68 72 6f 6e 6f 75 73 53
61 6d 70 6c 65 2e 50 72 6f 67 72 61 6d 2b 3c 47
65 74 44 61 74 61 3e 64 5f 5f 37 00 00
)
// Method begins at RVA 0x22dc
// Code size 54 (0x36)
.maxstack 2
.locals init (
[0] valuetype AsynchronousSample.Program/'<GetData>d__7',
[1] class [mscorlib]System.Threading.Tasks.Task`1<class AsynchronousSample.Data>,
[2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>
)
IL_0000: ldloca.s 0
IL_0002: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::Create()
IL_0007: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_000c: ldloca.s 0
IL_000e: ldc.i4.m1
IL_000f: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_0014: ldloca.s 0
IL_0016: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_001b: stloc.2
IL_001c: ldloca.s 2
IL_001e: ldloca.s 0
IL_0020: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::Start<valuetype AsynchronousSample.Program/'<GetData>d__7'>(!!0&)
IL_0025: ldloca.s 0
IL_0027: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_002c: call instance class [mscorlib]System.Threading.Tasks.Task`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::get_Task()
IL_0031: stloc.1
IL_0032: br.s IL_0034
IL_0034: ldloc.1
IL_0035: ret
} // end of method Program::GetData
В этом коде мы уже можем увидеть ключевое отличие. Это появление класса AsyncTaskMethodBuilder. Также добавляется код самой машины состояний.
.class nested private auto ansi sealed beforefieldinit '<GetData>d__7'
extends [mscorlib]System.ValueType
implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Fields
.field public int32 '<>1__state'
.field public valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> '<>t__builder'
.field public int64 '<result>5__8'
.field public class AsynchronousSample.Data '<>g__initLocal6'
.field private valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> '<>u__$awaiter9'
.field private object '<>t__stack'
// Methods
.method private final hidebysig newslot virtual
instance void MoveNext () cil managed
{
.override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
// Method begins at RVA 0x21c8
// Code size 232 (0xe8)
.maxstack 3
.locals init (
[0] bool '<>t__doFinallyBodies',
[1] class AsynchronousSample.Data '<>t__result',
[2] class [mscorlib]System.Exception '<>t__ex',
[3] int32 CS$4$0000,
[4] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> CS$0$0001,
[5] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> CS$0$0002,
[6] int64 CS$0$0003
)
.try
{
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldarg.0
IL_0003: ldfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_0008: stloc.3
IL_0009: ldloc.3
IL_000a: ldc.i4.s -3
IL_000c: beq.s IL_0014
IL_000e: ldloc.3
IL_000f: ldc.i4.0
IL_0010: beq.s IL_0019
IL_0012: br.s IL_001b
IL_0014: br IL_00b6
IL_0019: br.s IL_0058
IL_001b: br.s IL_001d
IL_001d: nop
IL_001e: call class [mscorlib]System.Threading.Tasks.Task`1<int64> AsynchronousSample.Program::GetTaskResult()
IL_0023: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [mscorlib]System.Threading.Tasks.Task`1<int64>::GetAwaiter()
IL_0028: stloc.s CS$0$0001
IL_002a: ldloca.s CS$0$0001
IL_002c: call instance bool valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>::get_IsCompleted()
IL_0031: brtrue.s IL_0077
IL_0033: ldarg.0
IL_0034: ldc.i4.0
IL_0035: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_003a: ldarg.0
IL_003b: ldloc.s CS$0$0001
IL_003d: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> AsynchronousSample.Program/'<GetData>d__7'::'<>u__$awaiter9'
IL_0042: ldarg.0
IL_0043: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_0048: ldloca.s CS$0$0001
IL_004a: ldarg.0
IL_004b: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::AwaitUnsafeOnCompleted<valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>, valuetype AsynchronousSample.Program/'<GetData>d__7'>(!!0&, !!1&)
IL_0050: nop
IL_0051: ldc.i4.0
IL_0052: stloc.0
IL_0053: leave IL_00e6
IL_0058: ldarg.0
IL_0059: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> AsynchronousSample.Program/'<GetData>d__7'::'<>u__$awaiter9'
IL_005e: stloc.s CS$0$0001
IL_0060: ldarg.0
IL_0061: ldloca.s CS$0$0002
IL_0063: initobj valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>
IL_0069: ldloc.s CS$0$0002
IL_006b: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> AsynchronousSample.Program/'<GetData>d__7'::'<>u__$awaiter9'
IL_0070: ldarg.0
IL_0071: ldc.i4.m1
IL_0072: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_0077: ldloca.s CS$0$0001
IL_0079: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>::GetResult()
IL_007e: ldloca.s CS$0$0001
IL_0080: initobj valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>
IL_0086: stloc.s CS$0$0003
IL_0088: ldarg.0
IL_0089: ldloc.s CS$0$0003
IL_008b: stfld int64 AsynchronousSample.Program/'<GetData>d__7'::'<result>5__8'
IL_0090: ldarg.0
IL_0091: newobj instance void AsynchronousSample.Data::.ctor()
IL_0096: stfld class AsynchronousSample.Data AsynchronousSample.Program/'<GetData>d__7'::'<>g__initLocal6'
IL_009b: ldarg.0
IL_009c: ldfld class AsynchronousSample.Data AsynchronousSample.Program/'<GetData>d__7'::'<>g__initLocal6'
IL_00a1: ldarg.0
IL_00a2: ldfld int64 AsynchronousSample.Program/'<GetData>d__7'::'<result>5__8'
IL_00a7: callvirt instance void AsynchronousSample.Data::set_Result(int64)
IL_00ac: nop
IL_00ad: ldarg.0
IL_00ae: ldfld class AsynchronousSample.Data AsynchronousSample.Program/'<GetData>d__7'::'<>g__initLocal6'
IL_00b3: stloc.1
IL_00b4: leave.s IL_00d0
IL_00b6: leave.s IL_00d0
} // end .try
catch [mscorlib]System.Exception
{
IL_00b8: stloc.2
IL_00b9: ldarg.0
IL_00ba: ldc.i4.s -2
IL_00bc: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_00c1: ldarg.0
IL_00c2: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_00c7: ldloc.2
IL_00c8: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::SetException(class [mscorlib]System.Exception)
IL_00cd: nop
IL_00ce: leave.s IL_00e6
} // end handler
IL_00d0: nop
IL_00d1: ldarg.0
IL_00d2: ldc.i4.s -2
IL_00d4: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_00d9: ldarg.0
IL_00da: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_00df: ldloc.1
IL_00e0: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::SetResult(!0)
IL_00e5: nop
IL_00e6: nop
IL_00e7: ret
} // end of method '<GetData>d__7'::MoveNext
.method private final hidebysig newslot virtual
instance void SetStateMachine (
class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine param0
) cil managed
{
.custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
01 00 00 00
)
.override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
// Method begins at RVA 0x22cc
// Code size 13 (0xd)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_0006: ldarg.1
IL_0007: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
IL_000c: ret
} // end of method '<GetData>d__7'::SetStateMachine
} // end of class <GetData>d__7
extends [mscorlib]System.ValueType
implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Fields
.field public int32 '<>1__state'
.field public valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> '<>t__builder'
.field public int64 '<result>5__8'
.field public class AsynchronousSample.Data '<>g__initLocal6'
.field private valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> '<>u__$awaiter9'
.field private object '<>t__stack'
// Methods
.method private final hidebysig newslot virtual
instance void MoveNext () cil managed
{
.override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
// Method begins at RVA 0x21c8
// Code size 232 (0xe8)
.maxstack 3
.locals init (
[0] bool '<>t__doFinallyBodies',
[1] class AsynchronousSample.Data '<>t__result',
[2] class [mscorlib]System.Exception '<>t__ex',
[3] int32 CS$4$0000,
[4] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> CS$0$0001,
[5] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> CS$0$0002,
[6] int64 CS$0$0003
)
.try
{
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldarg.0
IL_0003: ldfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_0008: stloc.3
IL_0009: ldloc.3
IL_000a: ldc.i4.s -3
IL_000c: beq.s IL_0014
IL_000e: ldloc.3
IL_000f: ldc.i4.0
IL_0010: beq.s IL_0019
IL_0012: br.s IL_001b
IL_0014: br IL_00b6
IL_0019: br.s IL_0058
IL_001b: br.s IL_001d
IL_001d: nop
IL_001e: call class [mscorlib]System.Threading.Tasks.Task`1<int64> AsynchronousSample.Program::GetTaskResult()
IL_0023: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [mscorlib]System.Threading.Tasks.Task`1<int64>::GetAwaiter()
IL_0028: stloc.s CS$0$0001
IL_002a: ldloca.s CS$0$0001
IL_002c: call instance bool valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>::get_IsCompleted()
IL_0031: brtrue.s IL_0077
IL_0033: ldarg.0
IL_0034: ldc.i4.0
IL_0035: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_003a: ldarg.0
IL_003b: ldloc.s CS$0$0001
IL_003d: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> AsynchronousSample.Program/'<GetData>d__7'::'<>u__$awaiter9'
IL_0042: ldarg.0
IL_0043: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_0048: ldloca.s CS$0$0001
IL_004a: ldarg.0
IL_004b: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::AwaitUnsafeOnCompleted<valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>, valuetype AsynchronousSample.Program/'<GetData>d__7'>(!!0&, !!1&)
IL_0050: nop
IL_0051: ldc.i4.0
IL_0052: stloc.0
IL_0053: leave IL_00e6
IL_0058: ldarg.0
IL_0059: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> AsynchronousSample.Program/'<GetData>d__7'::'<>u__$awaiter9'
IL_005e: stloc.s CS$0$0001
IL_0060: ldarg.0
IL_0061: ldloca.s CS$0$0002
IL_0063: initobj valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>
IL_0069: ldloc.s CS$0$0002
IL_006b: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64> AsynchronousSample.Program/'<GetData>d__7'::'<>u__$awaiter9'
IL_0070: ldarg.0
IL_0071: ldc.i4.m1
IL_0072: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_0077: ldloca.s CS$0$0001
IL_0079: call instance !0 valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>::GetResult()
IL_007e: ldloca.s CS$0$0001
IL_0080: initobj valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter`1<int64>
IL_0086: stloc.s CS$0$0003
IL_0088: ldarg.0
IL_0089: ldloc.s CS$0$0003
IL_008b: stfld int64 AsynchronousSample.Program/'<GetData>d__7'::'<result>5__8'
IL_0090: ldarg.0
IL_0091: newobj instance void AsynchronousSample.Data::.ctor()
IL_0096: stfld class AsynchronousSample.Data AsynchronousSample.Program/'<GetData>d__7'::'<>g__initLocal6'
IL_009b: ldarg.0
IL_009c: ldfld class AsynchronousSample.Data AsynchronousSample.Program/'<GetData>d__7'::'<>g__initLocal6'
IL_00a1: ldarg.0
IL_00a2: ldfld int64 AsynchronousSample.Program/'<GetData>d__7'::'<result>5__8'
IL_00a7: callvirt instance void AsynchronousSample.Data::set_Result(int64)
IL_00ac: nop
IL_00ad: ldarg.0
IL_00ae: ldfld class AsynchronousSample.Data AsynchronousSample.Program/'<GetData>d__7'::'<>g__initLocal6'
IL_00b3: stloc.1
IL_00b4: leave.s IL_00d0
IL_00b6: leave.s IL_00d0
} // end .try
catch [mscorlib]System.Exception
{
IL_00b8: stloc.2
IL_00b9: ldarg.0
IL_00ba: ldc.i4.s -2
IL_00bc: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_00c1: ldarg.0
IL_00c2: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_00c7: ldloc.2
IL_00c8: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::SetException(class [mscorlib]System.Exception)
IL_00cd: nop
IL_00ce: leave.s IL_00e6
} // end handler
IL_00d0: nop
IL_00d1: ldarg.0
IL_00d2: ldc.i4.s -2
IL_00d4: stfld int32 AsynchronousSample.Program/'<GetData>d__7'::'<>1__state'
IL_00d9: ldarg.0
IL_00da: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_00df: ldloc.1
IL_00e0: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::SetResult(!0)
IL_00e5: nop
IL_00e6: nop
IL_00e7: ret
} // end of method '<GetData>d__7'::MoveNext
.method private final hidebysig newslot virtual
instance void SetStateMachine (
class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine param0
) cil managed
{
.custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
01 00 00 00
)
.override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
// Method begins at RVA 0x22cc
// Code size 13 (0xd)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data> AsynchronousSample.Program/'<GetData>d__7'::'<>t__builder'
IL_0006: ldarg.1
IL_0007: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<class AsynchronousSample.Data>::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
IL_000c: ret
} // end of method '<GetData>d__7'::SetStateMachine
} // end of class <GetData>d__7
Первое впечатление от этого кода может вызвать приступ депрессии, потому что в нем
можно здорово сломать голову. В этом
плане IL-код смотреть
напрямую очень сложно и из ILSpy в данном
случае плохой помощник. Если смотреть через ILSpy,
то данный код не показывает сгенерированные компилятором методы, что очень плохо.
В данном случае нам лучше подойдет .NET Reflector,
который лучше показывает, что происходит внутри.
Итоги
[CompilerGenerated]
private struct <GetData>d__7 :
IAsyncStateMachine
{
// Fields
public int <>1__state;
public Data <>g__initLocal6;
public AsyncTaskMethodBuilder<Data> <>t__builder;
private object <>t__stack;
private TaskAwaiter<long> <>u__$awaiter9;
public long <result>5__8;
// Methods
private void MoveNext();
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine param0);
}
Рефлектор
показывает, что у нас была сгенерирована следующая структура данных, которая
наследуется от интерфейса IAsyncStateMachine. Интерфейс IAsyncStateMachine имеет два
метода: MoveNext и SetStateMachine.
Если мы раскроем полностью эту структуру, то увидим следующий сгенерированный
код для методов MoveNext и SetStateMachine:
[CompilerGenerated]
private struct <GetData>d__7 :
IAsyncStateMachine
{
public int <>1__state;
public Data <>g__initLocal6;
public AsyncTaskMethodBuilder<Data> <>t__builder;
private object <>t__stack;
private TaskAwaiter<long> <>u__$awaiter9;
public long <result>5__8;
private void MoveNext()
{
Data data;
try
{
TaskAwaiter<long> awaiter;
bool flag = true;
switch (this.<>1__state)
{
case -3:
goto Label_00D0;
case 0:
break;
default:
awaiter =
Program.GetTaskResult().GetAwaiter();
if (awaiter.IsCompleted)
{
goto Label_0077;
}
this.<>1__state = 0;
this.<>u__$awaiter9 =
awaiter;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<long>, Program.<GetData>d__7>(ref awaiter, ref this);
flag = false;
return;
}
awaiter = this.<>u__$awaiter9;
this.<>u__$awaiter9 = new TaskAwaiter<long>();
this.<>1__state = -1;
Label_0077:
long introduced7 =
awaiter.GetResult();
awaiter = new TaskAwaiter<long>();
long num2 = introduced7;
this.<result>5__8 = num2;
this.<>g__initLocal6 = new Data();
this.<>g__initLocal6.Result =
this.<result>5__8;
data = this.<>g__initLocal6;
}
catch (Exception exception)
{
this.<>1__state = -2;
this.<>t__builder.SetException(exception);
return;
}
Label_00D0:
this.<>1__state = -2;
this.<>t__builder.SetResult(data);
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine param0)
{
this.<>t__builder.SetStateMachine(param0);
}
}
Обратите внимание на использование метода MoveNext и goto для перехода к
состояниям. По сути, нам компилятор сгенерил обычный switch и переход между состояниями машины с помощью goto. Постараюсь объяснить, как это работает, на
примере напрямую в языке C#. Этот код будет подобен тому, который генерит компилятор
языка C#, за исключением того, что я его упростил по максимуму и
не использовал goto для перехода. После этого код стал более понятным и читабельным. Давайте создадим
новый метод GetStateMachineData, в котором
реализуем поведение, подобное тому, которое нам строит компилятор языка.
public static Task<Data> GetStateMachineData()
{
var builder = new AsyncTaskMethodBuilder<Data>();
int state = 0;
Action moveNext = null;
TaskAwaiter<long> awaiter = new TaskAwaiter<long>();
moveNext = () =>
{
try
{
if (state == 1)
{
builder.SetResult(new Data { Result = awaiter.GetResult()
});
return;
}
try
{
awaiter =
GetTaskResult().GetAwaiter();
if (!awaiter.IsCompleted)
{
state = 1;
awaiter.OnCompleted(moveNext);
return;
}
builder.SetResult(new Data { Result = awaiter.GetResult()
});
}
catch (Exception) { }
builder.SetResult(null);
}
catch (Exception exception)
{
builder.SetException(exception);
}
};
moveNext();
return builder.Task;
}
Использование этого метода ничем не отличается от предыдущих вариантов с Wait.
static void Main(string[] args)
{
Action action = () =>
{
var task = GetStateMachineData();
task.Wait();
Console.WriteLine(task.Result.Result);
};
action();
Console.ReadLine();
}
Если вы внимательно посмотрите на метод GetStateMachineData, то увидите, что я его, по сути, вызываю дважды. Первый раз
вызываю этот action в конце
метода, затем передаю в созданный мной класс TaskAwaiter в метод OnComplete,
который будет вызван, когда мы получим результат выполнения подсчета суммы
чисел.
Вы могли иногда стыкаться с таким кодом, когда пытались при использовании ключевых слов async/await использовать
ожидание таска, например, через метод Wait, и ваш интерфейс
попросту подвисал. Почему же не подвисало мое приложение? Потому что я не
использовал UI, который можете
использовать вы в своих примерах.
Код .NET всегда
исполняется в некотором контексте. Этот контекст определяет текущего
пользователя и другие значения, требуемые фреймворком. В некоторых контекстах
выполнения код работает в контексте синхронизации, который управляет
выполнением задач и другой асинхронной работы.
По-умолчанию после await код
продолжит работать в контексте, в котором он был запущен. Это удобно, потому
что, в основном, вы захотите, чтобы контекст безопасности был восстановлен, а также чтобы ваш код после await имел доступ к объектам Windows UI, если он
уже имел доступ к ним при старте.
Некоторые контексты
синхронизации не поддерживают повторный вход в них и являются однопоточными.
Это означает, что только одна единица работы может выполняться в этом контексте
одновременно. Примером этого может быть поток Windows UI или контекст ASP.NET.
В таких однопоточных
контекстах синхронизации довольно легко получить deadlock. Если вы создадите
задачу в однопоточном контексте и потом будете ждать в этом же контексте, ваш
код, который ждёт, будет блокировать выполнение фоновой задачи.
Например, если мы запустим следующий код в Windows UI, то можем запросто получить deadlock.
static void Main(string[] args)
{
Action action = () =>
{
var task = GetData().Result;
Console.WriteLine(task.Result);
};
action();
Console.ReadLine();
}
public static async Task<Data> GetData()
{
var result = await GetTaskResult();
return new Data { Result = result };
}
Мне очень понравилась статья Эрика Липперта "Real world async/await defects", где он продемонстрировал пример, когда deadlock может происходить явно. Код с примера с комментарием:
Frob GetFrob()
{
Frob result = null;
var networkDevice = new NetworkDevice();
networkDevice.OnDownload +=
async (state) => { result = await Frob.DeserializeStateAsync(state); };
networkDevice.GetSerializedState(); // Synchronous
return result;
}
Статья небольшая. Рекомендую ее прочесть, если у вас нет проблем с
английским языком. Также очень полезные ссылки, которые Липперт приводит в
статье, так как они позволяют глубже понять некую область.
Итоги
На этой ноте завершаю статью об использовании async/await. Надеюсь, если вы не знали раньше, как работает async/await, теперь
в этом плане будет немного проще. Если у вас возникли какие-то вопросы по
статье, буду рад на них ответить.
No comments:
Post a Comment