비동기 대기

22188 단어 csharpasync
주의: 이 글은 중국에서 온 경험과 교훈과 일치하도록 편집해야 합니다.
그렇습니다. 저는 마침내 async/await를 묘사할 준비가 되어 있습니다. 앞으로의 글에서 오류 사용과 관련된 흔한 성능 문제를 피할 수 있기를 바랍니다.
자신을 긴 댓글에 묶어라. 만약 당신이 async/await를 더 잘 이해하고 싶다면, 이것은 가치가 있다.

다시 말하다


나는 이미 묘사했다.나는 또한 당신이 사용하기를 희망할 수 있는 원인을 간략하게 묘사했다.그리고 나서 나는 또 간단한 소개를 했다.
왜 나는 직접 async/await 예시로 너를 비난하지 않고 이렇게 해야 합니까?스레드 탱크의 배고픔이나 응용 프로그램의 잠금장치로 인해 병이 나지 않도록 사용하는 베이스 스레드 모델을 알아야 한다고 생각합니다.(다음 시리즈에서 이 문제들을 어떻게 진단하는지 소개해 드리겠습니다.)

간단하게 말하자면, 무엇이 async/await입니까? 나는 왜 그것을 사용해야 합니까?


async와 await를 도입하기 전에, 우리가 비동기적인 행동을 얻기 위해 작성해야 할 코드가 매우 엉망이었다.코드가 도처에 있어서 인류는 이해하기 어렵다.
async/await를 사용하여 기본적으로 문법사탕을 제공하여 비동기 코드를 읽기 쉬운 동기 코드처럼 보였다.
우선 우리 동시 작업 좀 하자...
public class Example { public string Hello { get; set; } }

static void Main()
{
  // Let's go to sleep synchronously ...
  Thread.Sleep(1000);

  // Let's load some JSON files synchronously ...
  var example = JsonSerializer.Deserialize<Example>(File.ReadAllText("example.json"));

  // Let's make a web request synchronously ...
  var request = new HttpRequestMessage(HttpMethod.Get, "http://dev.to");
  var client = new HttpClient();
  var response = client.Send(request);
}
위의 예에서 컨트롤러 프로그램의 주 라인이 프로그램이 실행될 때까지 막혔다.
이제 우리 같은 일을 다른 걸음으로 하자...
public class Example { public string Hello { get; set; } }

static async Task Main()
{
  // let's sleep asynchronously ...
  await Task.Delay(1000);

  // Let's load some JSON files asynchronously ...
  await using FileStream stream = File.OpenRead("example.json");
  var example = await JsonSerializer.DeserializeAsync<Example>(stream);

  // Let's make a web request asynchronously ...
   var request = new HttpRequestMessage(HttpMethod.Get, "http://dev.to");
   var client = new HttpClient();
   var response = await client.SendAsync(request);
}
두 번째 예에서 코드와 동기화 예는 거의 같지만, 우리는 현재 Main의 서명에서 'await' 키워드와 'async Task' 를 사용합니다.
그래서 나는 네가 지금 말하는 것이'그러면 어때?'라고 추측한다.비동기 코드를 수정함으로써 우리는 온라인 스레드가 허용하는 최대 범위 내에서 모든 작업을 병행할 수 있다.
JSON 파일의 반서열화된 기준 테스트를 살펴보겠습니다.너는 코드를 건너뛸 수 있다. 왜냐하면 내가 진정으로 설명하고 싶은 것은 뒤의 도표이기 때문이다.
public class Example { public string Hello { get; set; } }

[RPlotExporter]
[SimpleJob(RunStrategy.ColdStart, RuntimeMoniker.Net50, baseline: true)]
public class Benchmarker
{
  [Params(16, 64, 256)] public int N;

  [Benchmark]
  public void A_SyncLoop() => A_SyncLoop(N);

  [Benchmark]
  public void B_SyncThreaded() => B_SyncThreaded(N);

  [Benchmark]
  public async Task C_AsyncLoop() => await C_AsyncLoop(N);

  [Benchmark]
  public async Task D_AsyncPooled() => await D_AsyncPooled(N);

  public void A_SyncLoop(int numberOfFiles)
  {
    for (var ii = 0; ii < numberOfFiles; ++ii)
    {
      var example = JsonSerializer.Deserialize<Example>(File.ReadAllText("example.json"));
    }
  }

  public void B_SyncThreaded(int numberOfFiles)
  {
    var threads = new Thread[numberOfFiles];
    for (var ii = 0; ii < numberOfFiles; ++ii)
    {
      threads[ii] = new Thread(() =>
      {
        var example = JsonSerializer.Deserialize<Example>(File.ReadAllText("example.json"));
      });
      // Don't block waiting for the result of this specific 
      // thread ...
      threads[ii].Start(); 
    }

    // ... block waiting for the results of all threads
    // running concurrently.
    for (var ii = 0; ii < numberOfFiles; ++ii)
      threads[ii].Join();
  }

  public async Task C_AsyncLoop(int numberOfFiles)
  {
    for (var ii = 0; ii < numberOfFiles; ++ii)
    {
      await using FileStream stream = File.OpenRead("example.json");
      var example = await JsonSerializer.DeserializeAsync<Example>(stream);
    }
  }

  // Note this code looks strange because want to 
  // separate the task creation from the task run. We cannot
  // simply return a task from an async method because it
  // will be started automatically. I have tried to make this
  // code look as similar as possible to the threaded 
  // implementation.
  public async Task D_AsyncPooled(int numberOfFiles)
  {
    var tasks = new Task[numberOfFiles];
    for (var ii = 0; ii < numberOfFiles; ++ii)
    {
      var func = new Func<Task>(async () =>
      {
        await using FileStream stream = File.OpenRead("example.json");
        var example = await JsonSerializer.DeserializeAsync<Example>(stream);
      });
      tasks[ii] = func.Invoke();
    }

    // ... await the results of all tasks running as concurrently as the
    // thread pool will allow.
    await Task.WhenAll(tasks);
  }
}

public static void Main()
{
  var summary = BenchmarkRunner.Run<Benchmarker>();
}
지금 가장 재미있는 부분은 도표...

이 그림에서 얻을 수 있는 유일한 정보는 반서열화할 파일의 수가 증가함에 따라 스레드 탱크를 사용하는 JSON 파일의 반서열화는 보통 가장 짧은 시간 안에 실행된다는 것이다.async/await 방법을 사용하여 스레드 탱크를 사용하는 것은 매우 간단합니다. 거의 동기화 코드처럼 보입니다!(async/await는 약간의 비용이 있기 때문에 동기화 방법이 소량의 파일에 더 빠를 수 있습니다. 그러나 파일 수량이 증가함에 따라 스레드 탱크가 진정으로 역할을 발휘하기 시작하면서 성능도 크게 향상되었습니다.)
UI 스레드에 관해서는 async/await를 사용할 때 머리를 쓸 필요가 없다.UI 스레드 수가 보통 1인 응답 UI를 얻으려면, UI 스레드가 막히지 않도록 async/await를 사용해야 합니다.

더 복잡한 것은:async/await가 무엇입니까, 제가 왜 그것을 사용해야 합니까?


주의해라, 내가 설명하고 해석한 후에 간단한 해석이 지금 더욱 미묘해졌다.이제 우리 더 깊이 들어가자...

과업


간단한 설명에서 우리가 직접 라인을 사용하는 것이 아니라 임무를 사용하기 시작했다는 것을 알 수 있습니다.이 간단한 예에서 Main의 서명에 나타나는 것을 제외하고는 임무가 나타나지 않았다.그러나 깊이 파고들수록 임무가 실제로 무엇인지 빨리 알아야 한다.
임무는 집행해야 할 어떤 업무 단원의 추상이다.임무는 장래에 어떤 일을 하기로 약속하고 선택적으로 결과를 되돌려준다.두 가지 종류의 작업이 있습니다. 하나는 값을 되돌려주지 않고, 다른 하나는 값을 되돌려줍니다.임무는 인용 형식이기 때문에 무더기로 분배됩니다.현재, 비동기적인 방법은 실제적으로 어떤 상황에서 동시 집행할 수 있기 때문에 (이것은 스레드 탱크에 달려 있기 때문에) 간단한 상황에서 당신은 상당히 많은 비용의 처벌을 받을 수 있다.C#7은 스택에 할당되는 양을 최소화하기 위해 새로운 ValueTask를 도입했습니다.

async 키워드


async 키워드는 컴파일러의 변환 방법과 비동기 상태기를 만드는 로고입니다.창고 추적이 어떤 모습인지 간단한 비동기 방법을 만들어 봅시다.
public class Example
{
  public static async Task GoToSleep()
  {
    // Note I should really be using a logger that uses the
    // async file API but I feel lazy so ...
    Console.WriteLine($"Async Thread ID: {Thread.CurrentThread.ManagedThreadId}");
    await Task.Delay(1000);
  }
}

class Program
{
  static async Task Main()
  {
    Console.WriteLine($"Main Thread ID: {Thread.CurrentThread.ManagedThreadId}");
    await Example.GoToSleep();
  }
}
이 프로그램의 출력은...
Main Thread ID: 1
Async Thread ID: 1
앞서 말씀드린 바와 같이, 저희는 컨디션 머신이 만든 비용 때문에 벌을 받을 것입니다. 창고 추적을 보여주는 것 외에는 아무런 이익이 없습니다.

따라서 코드를 디버깅할 때 너무 깊이 들어가지 않아도 일부 상태기가 운행하는 것을 볼 수 있다.C#5에서 async/await를 발표했을 때의 외관에 비해 현재의 창고 추적은 매우 직관적으로 보인다고 말해야 합니다!만약 당신이 정말로 당신의 손(예를 들어 this one을 더럽히고 싶다면, 많은 댓글이 있지만, 이것은 내가 토론하고자 하는 async와 같이 깊이 들어간다.

교환을 기다리다


wait 조작부호는 다른 방법이 완성될 때까지 문장의 실행을 정지합니다.async 방법이 T 형식의 작업을 되돌려주면, 값이나 T에 대한 인용을 되돌려줍니다.async 방법이 완성되면 결과를 되돌려주고 stations의 실행을 멈추지 않습니다.결과를 기다리는 라인이 막히지 않았음을 주의하십시오.

결론


나는 네가 이미 이 문장에서 async/Wait에 대해 더욱 깊이 이해하기를 바란다.보통, 나는 네가 많은 댓글을 읽어야만 이 화제를 더욱 깊이 이해할 수 있다는 것을 발견했다.더 읽고 싶다면 Stephen ToubStephen Cleary 같은 사람은 좋은 정보원이다.
다음 기사에서는 C#에서 비동기식/대기 방법을 잘못 사용한 것과 관련이 있는 일반적인 애플리케이션 오류 동작에 대해 살펴보겠습니다.

좋은 웹페이지 즐겨찾기