2017-04-10 4 views
0

내 WPF UI에 고객 목록이 있습니다. 또한 한 고객의 프로필을 가져 오기위한 웹 API 서비스가 있습니다. 서버 쪽과 클라이언트 쪽 모두 async/await을 사용하여 만들어졌으며 취소 할 수 있습니다.취소 가능한 요청 스트림에서 단일 비동기 웹 서비스 호출을 반복적으로 사용하는 방법은 무엇입니까?

사용자가 ComboBox에서 "Customer A"를 선택하면 서버 호출이 트리거되어 고객 프로필을 가져옵니다. 2 초 후 (서버 조치 방법의 고안된 기간) 데이터가 리턴되고 표시됩니다. 2 초 동안 "고객 B"가 선택되면 제 코드가 첫 번째 요청을 취소하고 두 번째 요청을 시작합니다.

이 모든 것이 효과적입니다. 사용자가 매우 다른 고객을 신속하게 선택하면 is-busy/cancellation 논리가 상당히 순진하며 제대로 취소되지 않습니다. 예를 들어, 아래쪽 화살표 키 (콤보 상자에 포커스가 있음)를 빠르게 10 번 누르면 5 회의 요청이 서버 측에서 올바르게 취소되고 5 회의 요청이 올바르게 처리되지 않습니다. 이것이 세계의 끝은 아니지만 잠재적으로 큰 데이터베이스 쿼리가 아무 이유없이 병렬로 실행되는 서버 리소스를 씹어서는 안됩니다. 새로운 UI 항목을 선택하면 모든 단일 요청이 취소되는 것을 보장 이런 종류의 일에 대한 몇 가지 잘 구축 된 패턴이 있어야합니다 같은 느낌

CancellationTokenSource _customerProfileCts; 

//called when a new customer item is selected from the UI's ComboBox 
private async void TriggerGetCustomerProfile() 
{ 
    //if there is already a customer profile fetch operation in progress, we just want to cancel it and start a new one. 
    if (IsBusyFetchingCustomerProfile) 
    { 
     _customerProfileCts.Cancel(); 
    } 

    try 
    { 
     IsBusyFetchingCustomerProfile = true; 
     IsCustomerProfileReady = false; 
     await GetCustomerProfile(); 
    } 
    finally 
    { 
     IsBusyFetchingCustomerProfile = false; 
     _customerProfileCts = null; 
    } 
} 

private async Task GetCustomerProfile() 
{ 
    _customerProfileCts = new CancellationTokenSource(); 
    await _customerSvc.GetCustomerProfileReport(SelectedCustomer.Id, _customerProfileCts.Token); 
    //logic for checking result of web call and distributing received data omitted 
} 

:

여기 내 클라이언트 코드 아무리 빨리 사용자가 선택 하든지간에.

실제로 리 액티브 확장 기능의 주요 사용 사례 중 하나가 아닙니까? 키 입력이 텍스트 상자에 입력 된 후 검색 웹 서비스를 호출하는 것과 같은 가설적인 문제에 대한 언급이 많이 있지만 이전에 보낸 요청을 취소하는 예는 발견하지 못했습니다.

나는 이런 종류의 취소 가능한 비동기 가져 오기가 문제가없는 내 응용 프로그램에서 사용할 수 있도록 깨끗하고 단단하고, 잘 포장 된 복잡성을 숨길 수있는 무언가가 필요합니다.

답변

3

문제는 finally_customerProfileCts이며 다른 호출에서는 여전히 사용중인 인스턴스가 null입니다. Cancel 이후로 이동하면 정상적으로 작동합니다. 실제로 GetCustomerProfile의 수정 사항과 함께 다음과 같이 조합 할 수 있습니다.

CancellationTokenSource _customerProfileCts; 

private async void TriggerGetCustomerProfile() 
{ 
    if (_customerProfileCts != null) 
    { 
    _customerProfileCts.Cancel(); 
    } 
    _customerProfileCts = new CancellationTokenSource(); 
    var token = _customerProfileCts.Token; 

    try 
    { 
    IsBusyFetchingCustomerProfile = true; 
    IsCustomerProfileReady = false; 
    await GetCustomerProfile(token); 
    } 
    finally 
    { 
    IsBusyFetchingCustomerProfile = false; 
    } 
} 

private async Task GetCustomerProfile(CancellationToken token) 
{ 
    await _customerSvc.GetCustomerProfileReport(SelectedCustomer.Id, token); 
} 
+1

정말 멋졌습니다. 내 'IsBusyFetchingCustomerProfile' 통화 중 표시기가 취소 된 통화의'finally '에서 다시 설정되는 작은 부작용이있었습니다. 새 통화가 높은 값을 설정 한 순간입니다. 당신의'AsyncLock'을 사용하여'try/finally'를 감싸고 문제를 해결했습니다. – BCA