2014-07-09 5 views
2

서버에서 데이터를 청크로 가져 와서 처리를 위해 반환하는 방법이 있습니다. 몇 가지 측정을했는데 백그라운드에서 청크를 다운로드하고 BlockingCollection<T>을 통해 반환하는 것이 상당히 빠르다는 것을 알았습니다. 이렇게하면 클라이언트와 서버가 서로 기다리지 않고 동시에 작업 할 수 있습니다.익명 작업에서 예외를 처리하려면 어떻게합니까?

public static IEnumerable<DataRecord> GetData(String serverAddress, Int64 dataID) 
{ 
    BlockingCollection<DataRecord> records = new BlockingCollection<DataRecord>(); 

    Task.Run(
     () => 
     { 
      Boolean isMoreData = false; 
      do 
      { 
       // make server request and process response 
       // this block can throw 

       records.Add(response.record); 
       isMoreData = response.IsMoreData; 
      } 
      while (isMoreData); 

      records.CompleteAdding(); 
     }); 

    return records.GetConsumingEnumerable(); 
} 

발신자 (C++/CLI 라이브러리)는 예외가 발생하여 다시 시도하거나 적절하게 복구 할 수 있음을 알아야합니다. 반환 유형을 최소한으로 변경하면서 호출자에게 예외를 전파하는 가장 좋은 방법은 무엇입니까?

+0

작업 공장 및 작업 계속 옵션을 고려 했습니까? – Darek

답변

2

이것은 화재 및 잊어 버린 작업이 일반적으로 좋지 않은 이유입니다. 그들은 finally 블럭 안에 과 함께 try/catch 안에 추가를 래핑하지 않으므로 GetConsumingEnumerable의 열거 자에있는 MoveNext에 대한 호출이 결국 무기한 차단된다는 것을 의미하므로 사용자의 경우에는 아이디어가 더 나빠집니다. 이는 나쁘지는 않습니다.

C# 경계 내에서 완전히 작업하는 경우 솔루션을 간단하게 만들 수 있습니다. 문제를보다 효과적으로 구분할 수 있습니다. BlockingCollection 비트를 제거하고 그것이 속한 곳, 소비자 (클라이언트) 또는 중간 파이프 라인 처리 단계 (궁극적으로 달성하려는 것)에서 실행하십시오. 생산자가 던진 예외의 귀하의 GetData 서명은 동일하게 유지하지만, 전체 예외 전파에 열거하는 간단한 차단된다 :

이제
var records = new BlockingCollection<DataRecord>(); 

var producer = Task.Run(() => 
{ 
    try 
    { 
     foreach (var record in GetData("http://foo.com/Service", 22)) 
     { 
      // Hand over the record to the 
      // consumer and continue enumerating. 
      records.Add(record); 
     } 
    } 
    finally 
    { 
     // This needs to be called even in 
     // exceptional scenarios so that the 
     // consumer task does not block 
     // indefinitely on the call to MoveNext. 
     records.CompleteAdding(); 
    } 
}); 

var consumer = Task.Run(() => 
{ 
    foreach (var record in records.GetConsumingEnumerable()) 
    { 
     // Do something with the record yielded by GetData. 
     // This runs in parallel with the producer, 
     // So you get concurrent download and processing 
     // with a safe handover via the BlockingCollection. 
    } 
}); 

await Task.WhenAll(producer, consumer); 

당신은 당신의 케이크가 있고 그것을 먹을 수

public static IEnumerable<DataRecord> GetData(String serverAddress, Int64 dataID) 
{ 
    Boolean isMoreData = false; 
    do 
    { 
     // make server request and process response 
     // this block can throw 

     yield return response.record; 
     isMoreData = response.IsMoreData; 
    } 
    while (isMoreData); 
} 

그런 다음 파이프 라인은 다음과 같습니다 레코드가 GetData에 의해 산출 될 때 처리가 병렬로 발생하고 생산자 작업에 await이 예외를 전파하는 반면 finally 내부에 CompleteAdding을 호출하면 소비자가 불명확 한 블로킹 상태에 빠지지 않도록 보장합니다. 어.

C++로 작업 했으므로 위의 내용은 어느 정도까지 적용 할 수 있습니다 (즉, C++에서 파이프 라인을 다시 구현하는 것이 옳은 일입니다). 그러나 구현이 너무 예쁘지는 않을 것입니다. 당신은 함께 갔지만 관찰되지 않은 작업으로 인해 해킹 된 것처럼 느껴질지라도 귀하의 대답은 선호되는 해결책 일 수 있습니다. 실제로 이 될 시나리오를 생각해 볼 수는 없습니다. CompleteAdding은 항상 새로 도입 된 try/catch으로 인해 호출됩니다.

분명히 다른 해결책은 처리 코드를 C# 프로젝트로 옮기는 것입니다. 이는 아키텍처에 따라 가능하지 않을 수도 있습니다.

+0

위대한 답변, 감사합니다!내가 추천하는 C# 파이프 라인을 사용할 수 있었으면 좋겠지 만 각 DataRecord를 해당 C++ 유형으로 변환하고 여러 계층의 MFC 불완전한 부분을 통과시켜야합니다. 내 솔루션은이 상황에서 실제로 더 깨끗하지만, 순수 C# 프로젝트의 어느 시점에서이 패턴을 사용할 가능성이 큽니다. – piedar

0

가장 간단한 해결책은 DataResult 컨텍스트를 반환하는 것입니다.이 컨텍스트에는 해당 레코드가 열거 된 후 예외가 포함될 수 있습니다.

예외가있는 경우 호출자 (C++/CLI)가이를 다시 throw 할 수 있습니다.

void Caller() 
{ 
    DataResult^ result = GetData("http://foo.com/Service", 22); 

    foreach (DataRecord record in result->Records) 
    { 
     // process records 
    } 

    Exception^ ex = result->Exception; 
    if (ex != nullptr) 
    { 
     throw ex; 
    } 
} 
관련 문제