편집 : 이 질문 은 같은 문제 일 것 같지만 응답이 없습니다 …
편집 : 테스트 사례 5에서 작업이 정지 된 것처럼 보입니다 WaitingForActivation
.
.NET 4.5에서 System.Net.Http.HttpClient를 사용하여 이상한 동작이 발생했습니다. 여기서 (예를 들어) 호출의 결과를 “대기”하면 httpClient.GetAsync(...)
반환되지 않습니다.
이는 새로운 비동기 / 대기 언어 기능 및 작업 API를 사용할 때 특정 상황에서만 발생합니다. 코드는 계속 사용하는 경우 항상 작동하는 것 같습니다.
다음은 문제를 재현하는 코드입니다. Visual Studio 11의 새로운 “MVC 4 WebApi 프로젝트”에 다음 GET 엔드 포인트를 표시하십시오.
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
여기서 각 끝점 /api/test5
은 완료되지 않은 것을 제외하고는 동일한 데이터 (stackoverflow.com의 응답 헤더)를 반환합니다 .
HttpClient 클래스에서 버그가 발생 했습니까, 아니면 어떤 식 으로든 API를 잘못 사용하고 있습니까?
재현 할 코드 :
public class BaseApiController : ApiController
{
/// <summary>
/// Retrieves data using continuations
/// </summary>
protected Task<string> Continuations_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
}
/// <summary>
/// Retrieves data using async/await
/// </summary>
protected async Task<string> AsyncAwait_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return result.Content.Headers.ToString();
}
}
public class Test1Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await Continuations_GetSomeDataAsync();
return data;
}
}
public class Test2Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = Continuations_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test3Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return Continuations_GetSomeDataAsync();
}
}
public class Test4Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await AsyncAwait_GetSomeDataAsync();
return data;
}
}
public class Test5Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = AsyncAwait_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test6Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return AsyncAwait_GetSomeDataAsync();
}
}
답변
API를 잘못 사용하고 있습니다.
상황은 다음과 같습니다. ASP.NET에서는 한 번에 하나의 스레드 만 요청을 처리 할 수 있습니다. 필요한 경우 병렬 처리를 수행 할 수 있지만 (스레드 풀에서 추가 스레드를 차용) 하나의 스레드 만 요청 컨텍스트를 갖습니다 (추가 스레드에는 요청 컨텍스트가 없음).
이것은 ASP.NET에 의해 관리됩니다SynchronizationContext
.
기본적으로 await
a Task
이면 메서드는 캡처 된 SynchronizationContext
(또는 TaskScheduler
없는 경우 캡처 된) 에서 다시 시작됩니다 SynchronizationContext
. 일반적으로 이것은 원하는 것입니다. 비동기 컨트롤러 작업은 await
무언가를하고 다시 시작하면 요청 컨텍스트와 함께 다시 시작됩니다.
따라서 test5
실패하는 이유 는 다음과 같습니다.
Test5Controller.Get
AsyncAwait_GetSomeDataAsync
ASP.NET 요청 컨텍스트 내에서 실행합니다 .AsyncAwait_GetSomeDataAsync
HttpClient.GetAsync
ASP.NET 요청 컨텍스트 내에서 실행합니다 .- HTTP 요청이 전송되고
HttpClient.GetAsync
미완료를 반환합니다Task
. AsyncAwait_GetSomeDataAsync
기다립니다Task
; 완료되지 않았으므로 미완료를AsyncAwait_GetSomeDataAsync
반환합니다Task
.Test5Controller.Get
Task
완료 될 때까지 현재 스레드를 차단 합니다 .- HTTP 응답이 들어오고로
Task
리턴됩니다HttpClient.GetAsync
. AsyncAwait_GetSomeDataAsync
ASP.NET 요청 컨텍스트 내에서 재개를 시도합니다. 그러나 해당 컨텍스트에는 이미 스레드가Test5Controller.Get
있습니다. 스레드가에 차단되었습니다 .- 이중 자물쇠.
다른 것들이 작동하는 이유는 다음과 같습니다.
- (
test1
,,test2
및test3
) : ASP.NET 요청 컨텍스트 외부 의Continuations_GetSomeDataAsync
스레드 풀로 연속을 예약합니다 . 이를 통해 요청 컨텍스트를 다시 입력하지 않고도 리턴 된 사용자 가 완료 될 수 있습니다 .Task
Continuations_GetSomeDataAsync
- (
test4
andtest6
) :Task
은 기다리고 있기 때문에 ASP.NET 요청 스레드는 차단되지 않습니다. 이를 통해AsyncAwait_GetSomeDataAsync
ASP.NET 요청 컨텍스트를 계속 사용할 수있게 됩니다.
모범 사례는 다음과 같습니다.
- “라이브러리”
async
방법에서ConfigureAwait(false)
가능할 때마다 사용 하십시오. 귀하의 경우이 변경AsyncAwait_GetSomeDataAsync
될 것입니다var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Task
s를 막지 마십시오 . 그건async
모든 방법을 아래로. 다시 말해,await
대신GetResult
(Task.Result
을Task.Wait
사용해야합니다await
).
이렇게하면 두 가지 이점이 있습니다. 연속 ( AsyncAwait_GetSomeDataAsync
메서드 의 나머지 )은 ASP.NET 요청 컨텍스트를 입력 할 필요가없는 기본 스레드 풀 스레드에서 실행됩니다. 컨트롤러 자체는 async
(요청 스레드를 차단하지 않습니다).
추가 정보:
- 내
async
/await
소개 게시물 . 여기에Task
대기자 사용 방법에 대한 간단한 설명이 포함되어 있습니다SynchronizationContext
. - 비동기 / 기다리고 있습니다 자주 묻는 질문 컨텍스트에 대한 자세한 내용이수록. Await, UI 및 교착 상태 도 참조하십시오 ! 어머! 어떤 수행 은 ASP.NET이 때문에, ASP.NET보다는 UI에있어 비록 여기에 적용
SynchronizationContext
한 번에 하나의 스레드에 요청 컨텍스트를 제한합니다. - 이 MSDN 포럼 게시물 .
- Stephen Toub 는 UI를 사용하여이 교착 상태를 시연 하고 Lucian Wischik도 시연 합니다.
2012-07-13 업데이트 : 이 답변 을 블로그 게시물에 통합 했습니다 .
답변
편집 : 일반적으로 교착 상태를 피하기위한 마지막 도랑 노력을 제외하고는 아래 작업을 피하십시오. Stephen Cleary의 첫 번째 의견을 읽으십시오.
여기 에서 빠른 수정 . 글을 쓰는 대신 :
Task tsk = AsyncOperation();
tsk.Wait();
시험:
Task.Run(() => AsyncOperation()).Wait();
또는 결과가 필요한 경우 :
var result = Task.Run(() => AsyncOperation()).Result;
소스에서 (위 예제와 일치하도록 편집) :
ThreadPool에서 AsyncOperation이 호출되어 SynchronizationContext가없고 AsyncOperation 내부에서 사용 된 연속은 호출 스레드로 다시 강제되지 않습니다.
나를 위해 이것은 비동기식으로 만들 수있는 옵션이 없기 때문에 사용 가능한 옵션처럼 보입니다.
출처에서 :
FooAsync 메소드의 대기가 마샬링 할 컨텍스트를 찾지 않는지 확인하십시오. 이를 수행하는 가장 간단한 방법은 ThreadPool에서 비동기 작업을 호출하는 것입니다.
int Sync () {return Task.Run (() => Library.FooAsync ()). Result; }
이제 SynchronizationContext가없는 ThreadPool에서 FooAsync가 호출되고 FooAsync 내부에서 사용 된 연속은 Sync ()를 호출하는 스레드로 다시 강제되지 않습니다.
답변
당신이 사용하고 있기 때문에 .Result
나 .Wait
또는 await
이는 원인이 종료됩니다 교착 상태를 코드에서.
당신이 사용할 수 ConfigureAwait(false)
에 async
대한 방법 교착 상태를 방지
이처럼 :
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
ConfigureAwait(false)
비동기 코드 차단 안함에 대해 가능 하면 사용할 수 있습니다 .
답변
이 두 학교는 실제로 제외되지 않습니다.
다음은 단순히 사용해야하는 시나리오입니다
Task.Run(() => AsyncOperation()).Wait();
또는 같은
AsyncContext.Run(AsyncOperation);
데이터베이스 트랜잭션 속성 아래에있는 MVC 작업이 있습니다. 아이디어는 문제가 발생했을 때 작업에서 수행 된 모든 것을 롤백하는 것입니다. 컨텍스트 전환이 허용되지 않으면 트랜잭션 롤백 또는 커밋이 실패합니다.
필요한 라이브러리는 비동기로 실행될 것으로 예상되므로 비동기입니다.
유일한 옵션입니다. 일반 동기화 호출로 실행하십시오.
나는 단지 그들 각자에게 말하고 있습니다.
답변
나는 OP와의 직접적인 관련성보다 완전성을 위해 여기에 더 많이 넣을 것입니다. 나는 HttpClient
응답을 다시 얻지 못한 이유를 궁금해 하면서 거의 하루 동안 요청을 디버깅했습니다 .
마지막으로 I가 잊고 있었던 것을 발견 호출 스택 아래로 더 호출.await
async
세미콜론이없는 것 같은 느낌이 듭니다.
답변
나는 여기에서 찾고있다 :
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
그리고 여기:
그리고보고 :
이 유형과 해당 멤버는 컴파일러에서 사용하도록 설계되었습니다.
고려 await
버전 일을하고, 일을의 권리 ‘방법이다, 당신은 정말이 질문에 대한 답변을해야합니까?
내 투표는 : API 오용 .
답변
