2017-12-02 4 views
0

최소한의 커플 링으로 많은 수의 작업자를 설정하려고하지만 C# async 및 작업을 사용하고 싶습니다. 모든 작업이 완전히 비동기적인 것은 아닙니다 (일부는 완전히 동기식입니다). 이렇게하기위한 동기는 비즈니스 로직을 실행하는 간단한 메소드를 만들고, System.Threading.Tasks.Task API를 사용하여 이들을 연쇄 적으로 연결하여 주문 개념을 유지하려는 것입니다. 기본적으로, 나는 첫번째 작업을 만들고, 몇개의 연속을 등록하고, 마지막 작업이 완료되기를 기다리고 싶다.이 작업이 일찍 돌아 오는 이유는 무엇입니까? 내가 잘못 했니?

:

void Main() 
{ 
    var worker = new Worker(); 

    var work = worker.StartWork(1, 2000); 
    work.ConfigureAwait(false); 

    var final = work.ContinueWith(_ => worker.StartWork(2, 0)) 
     .ContinueWith(ant => worker.StartWork(3, 1500)); 

    var awaiter = final.ContinueWith(_ => Tuple.Create(_.Id, _.Status)); 
    Console.WriteLine("\"final\" completed with result {0}", awaiter.Result); 
    Console.WriteLine("Done."); 
} 

// Define other methods and classes here 
class Worker { 
    internal async Task StartWork(int phase, int delay) { 
     Console.WriteLine("Entering phase {0} (in Task {1}) with {2} milliseconds timeout.", phase, Task.CurrentId, delay); 
     if (delay > 0) 
     { 
      Console.WriteLine("Do wait for {0} milliseconds.", delay); 
      await Task.Delay(delay); 
     } 

     Console.WriteLine("ending phase {0}", phase); 
    } 
} 

문제는 소위 awaiter 작업에 대기을 기다리고 것 같다 :

여기에 내가 무엇을 원하는 경우에도 작동하는지 확인하기 위해 단지 내장 된 간단한 프로토 타입입니다

Entering phase 1 (in Task) with 2000 milliseconds timeout. 
Do wait for 2000 milliseconds. 
ending phase 1 
Entering phase 2 (in Task 769) with 0 milliseconds timeout. 
ending phase 2 
Entering phase 3 (in Task 770) with 1500 milliseconds timeout. 
Do wait for 1500 milliseconds. 
"final" completed with result (770, RanToCompletion) 
Done. 
ending phase 3 

이것은 지원되지 않습니까? 나는 Task API를 꽤 잘 이해한다고 생각했지만 분명히 알지 못합니다. 나는 이것을 async 또는 task를 사용하지 않도록 변환 할 수 있다고 생각하며, 메소드를 완전히 동 기적으로 실행한다. 그러나 이것은 일을하는 나쁜 방법처럼 보인다. 내가 계속 실행하고자하는 것은 정확히 (이것들은 단지 CancellationToken을 수용합니다). 작업 사이의 메시지에는 특별한 의존성이 없습니다. 단지 주문의 개념을 유지할 필요가 있습니다.

감사합니다.

편집 : 위의 단어 awaiting을 잘못 사용했습니다. Task.Result에 액세스하는 것이 완전히 동기임을 알고 있습니다. 내 사과.

편집 2 : 내가 될 것으로 예상 무엇 ContinueWith(_ => worker.Start(2, 0))를 호출하면 ContinueWith에 작업을 반환 할 것, 그리고 내 사용자 대리인이 작업을 반환 할 때 TPL은 내부적으로 작업 worker.StartWork에 의해 반환 기다리고 있습니다 것입니다. ContinueWith에 대한 오버로드 목록을 보면 분명히 잘못되었습니다. 내가 해결하려고하는 부분은 작업을 스케쥴하는 방법 인 Main에서 기다리는 방법이다. 모든 대회가 끝나기 전에 나가고 싶지 않습니다.

  1. 의 주요 방법 세 가지 단계가 있습니다

    내가 ContinueWith을 사용하는 한 동기

    나는 다음과 같은 요구 사항을 가지고 있었다.
  2. 단계 1은 세 명의 작업자 (a, bc)를 만듭니다.
  3. 2 단계 작업 bc의 완료 후 추가 작업자 d 시작되고 ab 완료시 다른 작업자 e 유사한
  4. 처리를 (종속성 이러한 작업은 순서대로 생성되어야한다는 것이다) 이것은 모든 작업이 완료 될 때까지 계속됩니다.

내가 정확하게 코멘트에 의견을 이해한다면, 나는이 작업을 수행 할 수 있습니다 기본적으로 두 가지 방법이 있습니다

  1. 이 방법의 동기를 확인하고 연속성을 나 자신을 등록합니다.
  2. async Task으로 표시된 방법을 그대로두고 Task.WhenAll API와 함께 await 키워드를 사용하여 연속을 예약하십시오. 나는이 발생할 것으로 예상 무엇
+0

나는'TaskContinuationOptions.AttachedToParent'를 전달하지 않았기 때문에 문서를 검토하는 이유가 있다고 생각합니다. 그래서'Task' 연속이 detached 상태로 생성됩니다. 우연히 이것을 확인할 수있는 사람이 있습니까? – PSGuy

+3

다른 일이 일어날 것이라고 생각하는 이유에 대해 혼란 스럽습니다. 대부분의 작업을 기다리지 않는 비동기 워크 플로를 만들었습니다. 나는 계속해서 이렇게 말하고있다 : ** 비동기 워크 플로우 **에 대한 시퀀싱 작업이 기다리고있다. 시퀀싱을 거의 수행하지 않았으므로 모든 것이 순차적으로 실행되는 것은 아닙니다. –

+2

당신이 여기에서 일어날 것으로 예상했던 것과 당신이 그것을 기대 한 이유를 더 분명히 말하면, 누군가가 당신의 생각을 잘못 이해했는지 설명 할 수 있습니다. –

답변

4

케빈의 : 다른 언급이, 당신은 비동기을 사용할 수 있습니다/훨씬 쉽게 코드를 읽고 따라 (그리고 ContinueWith의 일부 코너 케이스에서 당신을 보호 할 것) 할 수 있도록 겠지만, 대신 기다리고 있습니다 대답은 좋다. 그러나이 의견을 바탕으로 "계속적으로"라는 말은 워크 플로의 순서를 설명 할 때 기다리는 것보다 더 많은 힘을줍니다. 그렇지 않습니다. 또한 명백한 연속성에 의지하지 않고 두 번째 편집의 워크 플로를 올바르게 구성하는 방법에 대한 문제는 다루어지지 않았습니다.

시나리오를 살펴 보겠습니다. 귀하의 작업 흐름은 다음과 같습니다. 우리는 A, B, C, D 및 E 작업을 가지고 있습니다. 시작 D는 B와 C의 완료에 달려 있습니다. E를 시작하는 것은 A와 B의 완료에 달려 있습니다.

쉽게 완료됩니다. 기억 : 은 작업에 대한 시퀀싱 작업을 기다리고 있습니다. "Y는 X 다음에 와야합니다."라고 말하기를 원할 때마다 Y가 시작되기 전에 X 을 기다리기 만하면됩니다.. 반대로, 어떤 일이 무언가보다 먼저 완수되기를 원하지 않는다면, 우리는 그 일을 기다리지 않습니다.

다음은 작은 프레임 워크입니다. 이 코드는 실제 코드를 작성하는 방법이 아니지만 워크 플로를 명확하게 보여줍니다.

private async Task DoItAsync(string s, int d) 
    { 
     Console.WriteLine($"starting {s}"); 
     await Task.Delay(d * 1000); 
     Console.WriteLine($"ending {s}"); 
    } 

    private async Task DoItAsync(Task pre1, Task pre2, string s, int d) 
    { 
     await pre1; 
     await pre2; 
     await DoItAsync(s, d); 
    } 

    private async void Form1_Load(object sender, EventArgs e) 
    { 
     Task atask = DoItAsync("A", 2); 
     Task btask = DoItAsync("B", 10); 
     Task ctask = DoItAsync("C", 2); 
     Task bcdtask = DoItAsync(btask, ctask, "D", 2); 
     Task abetask = DoItAsync(btask, atask, "E", 2); 
     await bcdtask; 
     await abetask; 
    } 

따라 수행하십시오. A, B 및 C가 시작됩니다. "기다리는 것"은 비동기로 만들지 않으므로 워크 플로에서 시퀀싱 포인트를 도입해야합니다.)

다음 두 개의 도우미 작업이 시작됩니다. D의 전제 조건은 B와 C이므로 B를 기다리고 있습니다. B가 불완전하다고 가정하고 호출자에게 작업을 반환합니다. "b와 c가 완료된 후 시작하고 d가 완료 될 때까지 기다리는 작업을 나타냅니다. ".

이제 우리는 두 번째 도우미 작업을 시작합니다. 다시 말하지만, B가 기다리고 있습니다. 불완전하다고 가정 해 봅시다. 우리는 발신자에게 돌아갑니다.

이제 워크 플로에 구조의 마지막 비트를 추가합니다. 두 개의 도우미 작업이 완료 될 때까지 워크 플로가 완료되지 않습니다.. 두 가지 도우미 작업은 D와 E가 완료 될 때까지 완료되지 않으며 D의 경우 B와 C, 또는 E의 경우 B와 A가 완료 될 때까지 시작되지 않습니다.

이 작은 프레임 워크를 사용하여 작업 완료 시점을 확인하고 await을 사용하여 워크 플로의 종속성을 구축하는 것이 매우 간단하다는 것을 알 수 있습니다. 그게 바로입니다. 이 비동기 적으로 완료 할 작업을 대기하기 때문에 대기 중입니다..

+0

이 에릭을 게시 주셔서 감사합니다. 나는이 문제를 시퀀싱 측면 때문에 대답으로 표시했습니다. 이것이 바로 제가 풀려고하는 문제입니다. – PSGuy

+0

@PSGuy : 꽤 괜찮습니다. 메시지 루프와 아파트 스레딩 모델이있는 환경에서 비동기에 대해 추론하기가 더 쉽기 때문에 여기에 Windows Forms를 사용하고 있습니다. 나는 비동기를위한 아이디어를 시도 할 때 콘솔 앱을 피하려고 노력한다. –

2

는 ContinueWith 호출 (_ => worker.Start (2 것은, 0)) ContinueWith에 작업을 반환이었다, 그리고 TPL은 worker.StartWork에 의해 반환되는 작업을 기다리고 내부적 것 내 사용자 대리인이 작업을 반환했습니다. ContinueWith에 대한 오버로드 목록을 보면 분명히 잘못되었습니다.

실은 당신이 놓친 것입니다. 귀하의 경우에 .ContinueWithTask<Task>을 반환하며 귀하가 예상 한대로 Task이 아닙니다. 즉,이 쉽게 하나 하나에 중첩 된 작업을 변환 할 Unwrap 방법을 사용하여 고정 할 수 있습니다 말했다 : 이것은 당신이 원하는 출력을 줄 것이다

var worker = new Worker(); 

var work = worker.StartWork(1, 2000); 

var final = work.ContinueWith(_ => worker.StartWork(2, 0)).Unwrap() 
    .ContinueWith(ant => worker.StartWork(3, 1500)).Unwrap(); 

var awaiter = final.ContinueWith(_ => Tuple.Create(_.Id, _.Status)); 
Console.WriteLine("\"final\" completed with result {0}", awaiter.Result); 
Console.WriteLine("Done."); 

.

static async Task DoWork() 
{ 
    var worker = new Worker(); 

    await worker.StartWork(1, 2000); 
    await worker.StartWork(2, 0); 
    await worker.StartWork(3, 1500); 
} 

static void Main(string[] args) 
{ 
    DoWork().Wait(); 
    Console.WriteLine("Done."); 
}