C #에서 비동기 메소드를 어떻게 작성합니까?


196

내가 읽은 모든 블로그 게시물은 C #에서 비동기 메서드를 사용하는 방법을 알려 주지만 이상한 이유로 소비 할 자체 비동기 메서드를 작성하는 방법을 설명하지 마십시오. 그래서 지금 내 코드를 사용하는이 코드가 있습니다.

private async void button1_Click(object sender, EventArgs e)
{
    var now = await CountToAsync(1000);
    label1.Text = now.ToString();
}

그리고 나는이 방법을 썼습니다 CountToAsync:

private Task<DateTime> CountToAsync(int num = 1000)
{
    return Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < num; i++)
        {
            Console.WriteLine("#{0}", i);
        }
    }).ContinueWith(x => DateTime.Now);
}

이것이 Task.Factory비동기 메소드를 작성하는 가장 좋은 방법입니까, 아니면 다른 방법으로 작성해야합니까?


22
방법을 구성하는 방법에 대한 일반적인 질문을하고 있습니다. 이미 동기화 된 메소드를 비동기 메소드로 전환하기 위해 어디서부터 시작 해야하는지 알고 싶습니다.
칼리드 아부 하크 메

2
자, 일반적인 동기 방법 무엇 이며 왜 비동기식으로 만들고 싶 습니까?
Eric Lippert

많은 파일을 일괄 처리하고 결과 객체를 반환해야한다고 가정 해 봅시다.
칼리드 아부 하크 메

1
(1) 대기 시간이 긴 작업이란 무엇입니까? 네트워크를 느리게 만들 수 있기 때문에 파일을 가져 오거나 처리를 수행하는 경우 CPU를 많이 사용하기 때문입니다. 그리고 (2) 당신은 왜 왜 당신이 처음에 비 동기화되고 싶어하는지 말하지 않았습니다. 차단하지 않으려는 UI 스레드가 있습니까?
Eric Lippert

21
@EricLippert op가 제공하는 예제는 매우 기본적이므로 실제로 그렇게 복잡 할 필요는 없습니다.
David B.

답변:


227

StartNew복잡한 수준이 필요 하지 않으면 권장하지 않습니다 .

비동기 메소드가 다른 비동기 메소드에 종속되는 경우 가장 쉬운 방법은 async키워드 를 사용하는 것입니다 .

private static async Task<DateTime> CountToAsync(int num = 10)
{
  for (int i = 0; i < num; i++)
  {
    await Task.Delay(TimeSpan.FromSeconds(1));
  }

  return DateTime.Now;
}

비동기 메소드가 CPU 작업을 수행하는 경우 다음을 사용해야합니다 Task.Run.

private static async Task<DateTime> CountToAsync(int num = 10)
{
  await Task.Run(() => ...);
  return DateTime.Now;
}

async/ await소개가 도움 이 될 수 있습니다.


10
@Stephen : "비동기 메소드가 다른 비동기 메소드에 의존하는 경우"-좋습니다. 그러나 그렇지 않은 경우 어떻게해야합니까? 콜백 코드로 BeginInvoke를 호출하는 일부 코드를 래핑하려는 경우 어떻게됩니까?
Ricibob

1
@Ricibob : TaskFactory.FromAsync을 감싸는 데 사용해야 합니다 BeginInvoke. "콜백 코드"에 대해 무슨 뜻인지 잘 모르겠습니다. 코드로 자신의 질문을 자유롭게 게시하십시오.
Stephen Cleary

@ Stephen : 감사합니다-예 TaskFactory.FromAsync는 내가 찾고있는 것입니다.
Ricibob

1
for 루프에서 Stephen은 다음 반복이 즉시 또는 Task.Delay리턴 후에 호출 됩니까?
jp2code

3
@ jp2code : await는 "비동기 대기"이므로 작업이 반환 한 후 Task.Delay완료 될 때까지 다음 반복으로 진행되지 않습니다 .
Stephen Cleary

12

메소드 내에서 async / await를 사용하고 싶지 않지만 외부에서 await 키워드를 사용할 수 있도록 여전히 "장식"하면 TaskCompletionSource.cs :

public static Task<T> RunAsync<T>(Func<T> function)
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ =>          
    { 
        try 
        {  
           T result = function(); 
           tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
   }); 
   return tcs.Task; 
}

여기여기에서

Tasks와 같은 패러다임을 지원하려면 Task façade를 유지하는 방법과 임의의 비동기 작업을 Task로 참조하는 기능이 필요하지만 기본 인프라의 규칙에 따라 해당 Task의 수명을 제어해야합니다. 비 동시성이고, 비용이 많이 들지 않는 방식으로 그렇게하는 것. 이것이 TaskCompletionSource의 목적입니다.

예를 들어 .NET 소스에서도 사용됩니다. WebClient.cs :

    [HostProtection(ExternalThreading = true)]
    [ComVisible(false)]
    public Task<string> UploadStringTaskAsync(Uri address, string method, string data)
    {
        // Create the task to be returned
        var tcs = new TaskCompletionSource<string>(address);

        // Setup the callback event handler
        UploadStringCompletedEventHandler handler = null;
        handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.UploadStringCompleted -= completion);
        this.UploadStringCompleted += handler;

        // Start the async operation.
        try { this.UploadStringAsync(address, method, data, tcs); }
        catch
        {
            this.UploadStringCompleted -= handler;
            throw;
        }

        // Return the task that represents the async operation
        return tcs.Task;
    }

마지막으로 다음 사항도 유용하다는 것을 알았습니다.

나는 항상이 질문을 받는다. 그 의미는 외부 리소스에 대한 I / O 호출을 차단하는 어딘가에 스레드가 있어야한다는 것입니다. 따라서 비동기 코드는 요청 스레드를 비우지 만 시스템의 다른 곳에서는 다른 스레드를 희생해야합니다. 아뇨, 전혀 아닙니다. 비동기 요청이 확장되는 이유를 이해하기 위해 비동기 I / O 호출의 단순화 된 예를 살펴 보겠습니다. 요청이 파일에 기록되어야한다고 가정 해 봅시다. 요청 스레드는 비동기 쓰기 메소드를 호출합니다. WriteAsync는 BCL (Base Class Library)에 의해 구현되며 비동기 I / O에 완료 포트를 사용합니다. 따라서 WriteAsync 호출은 비동기 파일 쓰기로 OS에 전달됩니다. 그런 다음 OS는 드라이버 스택과 통신하여 데이터를 전달하여 I / O 요청 패킷 (IRP)에 씁니다. 이곳은 흥미로운 일입니다. 장치 드라이버가 IRP를 즉시 처리 할 수없는 경우 비동기 적으로 처리해야합니다. 따라서 드라이버는 디스크에 쓰기 시작을 지시하고 OS에 "보류 중"응답을 반환합니다. OS는 해당“보류”응답을 BCL에 전달하고 BCL은 불완전한 작업을 요청 처리 코드로 반환합니다. 요청 처리 코드가 작업을 기다리고 있으며,이 작업은 해당 메소드 등에서 불완전한 작업을 반환합니다. 마지막으로 요청 처리 코드가 완료되지 않은 작업을 ASP.NET으로 반환하고 요청 스레드가 해제되어 스레드 풀로 돌아갑니다. 요청 처리 코드가 작업을 기다리고 있으며,이 작업은 해당 메소드 등에서 불완전한 작업을 반환합니다. 마지막으로 요청 처리 코드가 완료되지 않은 작업을 ASP.NET으로 반환하고 요청 스레드가 해제되어 스레드 풀로 돌아갑니다. 요청 처리 코드가 작업을 기다리고 있으며,이 작업은 해당 메소드 등에서 불완전한 작업을 반환합니다. 마지막으로 요청 처리 코드가 완료되지 않은 작업을 ASP.NET으로 반환하고 요청 스레드가 해제되어 스레드 풀로 돌아갑니다.

ASP.NET의 Async / Await 소개

목표가 응답 성이 아닌 확장 성을 향상시키려는 경우,이를 수행 할 수있는 기회를 제공하는 외부 I / O의 존재에 의존합니다.


1
당신은 위의 코멘트를 볼 경우 Task.Run 구현ThreadPool이 실행되도록 지정된 작업을 대기열을하고 그 일에 대한 작업 핸들을 반환합니다 . 당신은 실제로 똑같이합니다. Task.Run이 더 좋을 것이라고 확신합니다. 내부적으로 CancelationToken을 관리하고 예약 및 실행 옵션을 사용하여 일부 최적화를 수행합니다.
unsafePtr

ThreadPool.QueueUserWorkItem으로 수행 한 작업을 Task.Run으로 수행 할 수 있습니까?
Razvan

-1

메서드를 비동기 적으로 만드는 간단한 방법 중 하나는 Task.Yield () 메서드를 사용하는 것입니다. MSDN은 다음과 같이 말합니다.

await Task.Yield ();를 사용할 수 있습니다. 비동기 메소드에서 강제로 메소드가 비동기 적으로 완료되도록합니다.

메소드의 시작 부분에 삽입하면 호출자에게 즉시 리턴되어 다른 스레드에서 나머지 메소드를 완료합니다.

private async Task<DateTime> CountToAsync(int num = 1000)
{
    await Task.Yield();
    for (int i = 0; i < num; i++)
    {
        Console.WriteLine("#{0}", i);
    }
    return DateTime.Now;
}

WinForms 애플리케이션에서 나머지 메소드는 동일한 스레드 (UI 스레드)에서 완료됩니다. 는 Task.Yield()당신이 반환되도록 할 경우에, 참으로 유용 Task생성 즉시 완료되지 않습니다.
Theodor Zoulias
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.