2012-10-19 2 views
23

다음은 문제가있는 코드의 단순화 된 버전입니다. 콘솔 응용 프로그램에서 실행할 때 예상대로 작동합니다. 모든 쿼리는 병렬로 실행되며 모두 완료되면 Task.WaitAll()이 반환됩니다.Task.WaitAll ASP.NET에서 대기중인 여러 작업에 걸려있다

그러나이 코드가 웹 응용 프로그램에서 실행되면 요청이 중단됩니다. 디버거를 연결하고 모두 중단하면 실행이 Task.WaitAll()에서 대기 중임을 보여줍니다. 첫 번째 과제는 완료되었지만 다른 과제는 완료되지 않습니다.

ASP.NET에서 실행될 때 왜 멈추는 지 알 수는 없지만 콘솔 응용 프로그램에서는 제대로 작동합니다.

public Foo[] DoWork(int[] values) 
{ 
    int count = values.Length; 
    Task[] tasks = new Task[count]; 

    for (int i = 0; i < count; i++) 
    { 
     tasks[i] = GetFooAsync(values[i]); 
    } 

    try 
    { 
     Task.WaitAll(tasks); 
    } 
    catch (AggregateException) 
    { 
     // Handle exceptions 
    } 

    return ... 
} 

public async Task<Foo> GetFooAsync(int value) 
{ 
    Foo foo = null; 

    Func<Foo, Task> executeCommand = async (command) => 
    { 
     foo = new Foo(); 

     using (SqlDataReader reader = await command.ExecuteReaderAsync()) 
     { 
      ReadFoo(reader, foo); 
     } 
    }; 

    await QueryAsync(executeCommand, value); 

    return foo; 
} 

public async Task QueryAsync(Func<SqlCommand, Task> executeCommand, int value) 
{ 
    using (SqlConnection connection = new SqlConnection(...)) 
    { 
     connection.Open(); 

     using (SqlCommand command = connection.CreateCommand()) 
     { 
      // Set up query... 

      await executeCommand(command); 

      // Log results... 

      return; 
     } 
    }   
} 

답변

46

Task.WaitAll 대신 await Task.WhenAll을 사용해야합니다.

ASP.NET에는 실제 동기화 컨텍스트가 있습니다. 즉, await이 호출 된 후에는 해당 컨텍스트로 마샬링되어 연속을 실행합니다 (효과적으로 이러한 연속을 직렬화 함). 콘솔 앱에는 동기화 컨텍스트가 없으므로 모든 연속성이 스레드 풀로 보내집니다. 요청의 컨텍스트에서 Task.WaitAll을 사용하면 차단하고 있으므로 다른 모든 작업의 ​​지속을 처리하는 데 사용되지 않습니다.

ASP 응용 프로그램에서 async/await의 주요 이점 중 하나는 이 아니고이 아니라 요청을 처리하는 데 사용중인 스레드 풀 스레드를 차단한다는 것입니다. Task.WaitAll을 사용하면 그 목적을 상실하게됩니다.

이 변경을 수행하는 부작용은 차단 조작에서 대기 조작으로 이동하면 예외가 다르게 전파됩니다. AggregateException을 던지기보다는 기본 예외 중 하나를 던집니다.

+1

+1. 비록 "메인 쓰레드"대신 "요청 컨텍스트"라는 용어를 사용 하겠지만. –

+0

@StephenCleary 예, 그것은 논쟁의 여지가있었습니다. 전체 응용 프로그램의 기본 스레드가 아니기 때문에 주 스레드를 따옴표로 묶었지만 "해당 요청의 주 스레드"라고 생각할 수 있습니다. 그것은 내 마음 속의 문제를 생각할 수있는 유용한 방법입니다. – Servy

+4

중요한주의 사항 중 하나는 Task.WhenAll이 AggregateException을 throw하지 않을 것임을 기다리는 것입니다. 내부 예외 중 하나만 전파됩니다. 전체 AggregateException을 검사하려면 task.WhenAll에서 반환 된 작업에 대한 참조를 저장하고 Exception 속성을 명시 적으로 검사해야합니다. –

관련 문제