0

백그라운드 작업에서 수행 할 작업이 오래 실행되었습니다. 작업이 끝나면 작업이 완료되었음을 알리고 싶습니다. 그래서 본질적으로 백그라운드에서 실행하고 싶은 두 개의 비동기 작업이 있습니다.

나는 계속 작업을하고 있지만, 계속 작업은 초기 작업이 끝나기 전에 시작됩니다. 예상되는 동작은 초기 작업이 완료된 후에 만 연속이 실행된다는 것입니다.

문제를 나타내는 몇 가지 예제 코드는 다음과 같습니다.

// Setup task and continuation
var task = new Task(async () =>
{
    DebugLog("TASK Starting");
    await Task.Delay(1000);     // actual work here
    DebugLog("TASK Finishing");
});

task.ContinueWith(async (x) =>
{
    DebugLog("CONTINUATION Starting");
    await Task.Delay(100);      // actual work here
    DebugLog("CONTINUATION Ending");
});

task.Start();

DebugLog 함수는 다음과 같습니다.

static void DebugLog(string s, params object[] args)
{
    string tmp = string.Format(s, args);
    System.Diagnostics.Debug.WriteLine("{0}: {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), tmp);
}

예상 출력 :

TASK Starting
TASK Finishing
CONTINUATION Starting
CONTINUATION Ending

실제 출력 :

TASK Starting
CONTINUATION Starting
CONTINUATION Ending
TASK Finishing

다시, 제 질문은 왜 초기 작업이 완료되기 전에 계속 작업이 시작되는지입니다. 첫 번째 작업이 완료된 후에 만 연속체를 실행하려면 어떻게해야합니까?


해결 방법 #1

intial 작업을 동기식으로 만들면 위 코드가 예상대로 작동 할 수 있습니다. 즉,Wait~에Task.Delay이렇게 :    

var task = new Task(() =>
{
    DebugLog("TASK Starting");
    Task.Delay(1000).Wait();     // Wait instead of await
    DebugLog("TASK Finishing");
});

많은 이유로, 사용하는 것은 나쁘다.Wait이렇게. 한 가지 이유는 스레드를 차단한다는 것이고, 이것은 피하고 싶은 것입니다.


해결 방법 #2

내가 작업 생성을 가져 와서 그것을 자신의 함수로 옮기면 그것은 잘 작동하는 것처럼 보인다 :

// START task and setup continuation
var task = Test1();
task.ContinueWith(async (x) =>
{
    DebugLog("CONTINUATION Starting");
    await Task.Delay(100);      // actual work here
    DebugLog("CONTINUATION Ending");
});

static public async Task Test1()
{
    DebugLog("TASK Starting");
    await Task.Delay(1000);     // actual work here
    DebugLog("TASK Finishing");
}

위의 접근 방식에 대한 크레딧은 다소 관련있는 (그러나 중복되지는 않음) 질문에 적용됩니다.Task.ContinueWith와 함께 비동기 콜백 사용


해결 방법 #2는 해결 방법 #1보다 낫습니다. 위의 초기 코드가 작동하지 않는 이유에 대한 설명이 없으면 취할 수있는 방법 일 가능성이 큽니다.

3 답변


4

귀하의 원래 코드가 작동하지 않습니다.async람다는async void메서드는 완료된 다른 코드에 통보 할 기본 제공 방법이 없습니다. 그래서, 당신이보고있는 것은async voidasync Task. 이것은 피해야하는 이유 중 하나입니다.async void.

즉, 코드로 할 수 있다면,Task.Run대신에Task생성자와Start; 사용await오히려ContinueWith:

var task = Task.Run(async () =>
{
  DebugLog("TASK Starting");
  await Task.Delay(1000);     // actual work here
  DebugLog("TASK Finishing");
});
await task;
DebugLog("CONTINUATION Starting");
await Task.Delay(100);      // actual work here
DebugLog("CONTINUATION Ending");

Task.Runawait더 자연스럽게 일하다.async암호.


  • -1 : 원래 코드가 작동하지 않기 때문에 작동하지 않습니다.Unwrap()필요한 경우. 창조의async void람다 (lambda)는 분명히 바람직하지 않지만, 사용하지 않는 부작용 만은 아니다.new Task<Task>(...)그래서Unwrap()작동 할 것이다. - Sam Harwell
  • @ 280Z28 : 예, 원본 코드를 해킹하여 사용할 수 있습니다.new Task<Task>Unwrap; 그러나 새로운 코드가 작동합니다.때문에그것은async void람다. 그리고 그것은 여전히 좋은만큼 좋지 않을 것입니다.Task.Run기반 솔루션. - Stephen Cleary
  • +1 나는 결국 사용Task.Run그리고 원래의 연속. 기존 코드가 작동하지 않는 이유에 대한 통찰력을 주셔서 감사합니다 : 람다가async void. 물론 그 말을 할 때 모든 것이 더 의미가있었습니다. 그렇다면 나는 왜 그 연속을 사용하고 있는가? 여기에 설명하려고하는 것이 너무 많습니다.하지만 할 일이 많이 있습니다.이 질문. - chue x

4

특히 비동기 작업을 시작할 때 Task 생성자를 직접 사용하여 작업을 시작해서는 안됩니다. 백그라운드에서 실행될 작업을 오프로드하려는 경우Task.Run대신 :

var task = Task.Run(async () =>
{
    DebugLog("TASK Starting");
    await Task.Delay(1000);     // actual work here
    DebugLog("TASK Finishing");
});

계속에 관해서는, 람다 식의 끝 부분에 논리를 덧붙이는 것이 낫습니다. 하지만 당신이ContinueWith너는 사용할 필요가있어.Unwrap실제 비동기 작업을 가져 와서 예외를 처리 할 수 있도록 저장하십시오.

task = task.ContinueWith(async (x) =>
{
    DebugLog("CONTINUATION Starting");
    await Task.Delay(100);      // actual work here
    DebugLog("CONTINUATION Ending");
}).Unwrap();

try
{
    await task;
}
catch
{
    // handle exceptions
}


  • +1 고마워,Task.Run내가 필요한 것입니다. 내가 본 MSDN 연속 예제 때문에 그것을 결코 고려하지 않았다.이 같은용도new Task. Stephen의 대답에 동의합니다.위의 코드가 작동하지 않습니다. 내 초기 작업 람다는async void대신에async Task. 그렇다면 나는 왜 그 연속을 사용하고 있는가? 여기에 설명하려고하는 것이 너무 많습니다.하지만 할 일이 많이 있습니다.이 질문. - chue x

0

코드를 다음으로 변경하십시오.

        // Setup task and continuation
        var t1 = new Task(() =>
        {
            Console.WriteLine("TASK Starting");
            Task.Delay(1000).Wait();     // actual work here
            Console.WriteLine("TASK Finishing");
        });

        var t2 = t1.ContinueWith((x) =>
        {
            Console.WriteLine("C1");
            Task.Delay(100).Wait();      // actual work here
            Console.WriteLine("C2");
        });

        t1.Start();

        // Exception will be swallow without the line below
        await Task.WhenAll(t1, t2);


  • 나는 보통 사용을 피하려고 노력한다.Wait실행중인 스레드를 차단하고 잠재적으로 스레드가 교착 상태가 될 수 있기 때문입니다. 만나다이 포스트교착 상태에 대한 자세한 정보는 게시물 사용Result대신에Wait하지만 둘 다 실행중인 스레드를 차단하고 잠재적으로 교착 상태를 유발할 수 있습니다. - chue x

연결된 질문


관련된 질문

최근 질문