Zacznijmy od synchronicznego kodu:
internal class Program { private static void Main(string[] args) { ShortMethod1(); ShortMethod2(); ShortMethod3(); } private static void ShortMethod1() { Console.WriteLine("1"); } private static void ShortMethod2() { Console.WriteLine("2"); } private static void ShortMethod3() { Console.WriteLine("3"); } }
Mamy powyżej przykład 3 metod, które wykonują bardzo proste operacje. Nie są one zbyt skomplikowane i z pewnością mogłyby być scalone do jednej metody. Z punktu widzenia wydajnościowego nie ma to jednak większego znaczenia. Prawdopodobnie kompilator zoptymalizuje to i wywoła metody w sposób inline, co tym bardziej nie będzie miało znaczenia na wydajność.
Podsumowując, dla metod synchronicznych nie ma znaczenia czy grupujemy operacje w jedną metodę czy mamy interfejs dostarczający atomowe metody.
Przyjrzyjmy się teraz metodom asynchronicznym:
internal class Program { private static void Main(string[] args) { ShortMethod1Async(); ShortMethod2Async(); ShortMethod3Async(); } private static async void ShortMethod1Async() { Console.WriteLine("1"); } private static async void ShortMethod2Async() { Console.WriteLine("2"); } private static async void ShortMethod3Async() { Console.WriteLine("3"); } }
Proszę zauważyć, że dodaliśmy tylko jedno słowo kluczowe. Po przeczytaniu poprzedniego wpisu powinno być jasne, dlaczego powyższy kod nie jest dobrym rozwiązaniem. Dla każdej metody zostanie wygenerowana maszyna stanów:
internal class Program { // Methods public Program(); private static void Main(string[] args); [AsyncStateMachine(typeof(<ShortMethod1Async>d__0)), DebuggerStepThrough] private static void ShortMethod1Async(); [AsyncStateMachine(typeof(<ShortMethod2Async>d__2)), DebuggerStepThrough] private static void ShortMethod2Async(); [DebuggerStepThrough, AsyncStateMachine(typeof(<ShortMethod3Async>d__4))] private static void ShortMethod3Async(); // Nested Types [CompilerGenerated] private struct <ShortMethod1Async>d__0 : IAsyncStateMachine { // Fields public int <>1__state; public AsyncVoidMethodBuilder <>t__builder; // Methods private void MoveNext(); [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine param0); } [CompilerGenerated] private struct <ShortMethod2Async>d__2 : IAsyncStateMachine { // Fields public int <>1__state; public AsyncVoidMethodBuilder <>t__builder; // Methods private void MoveNext(); [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine param0); } [CompilerGenerated] private struct <ShortMethod3Async>d__4 : IAsyncStateMachine { // Fields public int <>1__state; public AsyncVoidMethodBuilder <>t__builder; // Methods private void MoveNext(); [DebuggerHidden] private void SetStateMachine(IAsyncStateMachine param0); } }
Ciało ShortMethod wygląda następująco:
[AsyncStateMachine(typeof(<ShortMethod1Async>d__0)), DebuggerStepThrough] private static unsafe void ShortMethod1Async() { <ShortMethod1Async>d__0 d__; AsyncVoidMethodBuilder builder; &d__.<>t__builder = AsyncVoidMethodBuilder.Create(); &d__.<>1__state = -1; builder = &d__.<>t__builder; &builder.Start<<ShortMethod1Async>d__0>(&d__); return; }
MoveNext z kolei zawiera:
private unsafe void MoveNext() { bool flag; Exception exception; Label_0000: try { flag = 1; Console.WriteLine("1"); goto Label_0025; } catch (Exception exception1) { Label_000E: exception = exception1; this.<>1__state = -2; &this.<>t__builder.SetException(exception); goto Label_0038; } Label_0025: this.<>1__state = -2; &this.<>t__builder.SetResult(); Label_0038: return; }
Innymi słowy, kompilator nigdy tego nie wykona inline. Z tego względu należy przemyśleć naszą strategie implementowania kodu asynchronicznego. Takie metody zdecydowanie powinny wykonywać operacje w batch’u, a nie atomowo. Dla programisty, w c# kod wygląda bardzo prosto (tylko jedno słowo kluczowe), ale w rzeczywistości wykonujemy BARDZO wiele operacji.
Podsumowując: synchroniczne metody mogą zawierać atomowe operacje, z kolei asynchroniczne powinny wykonywać zadania grupowo.