백그라운드 작업에서 수행 할 작업이 오래 실행되었습니다. 작업이 끝나면 작업이 완료되었음을 알리고 싶습니다. 그래서 본질적으로 백그라운드에서 실행하고 싶은 두 개의 비동기 작업이 있습니다.
나는 계속 작업을하고 있지만, 계속 작업은 초기 작업이 끝나기 전에 시작됩니다. 예상되는 동작은 초기 작업이 완료된 후에 만 연속이 실행된다는 것입니다.
문제를 나타내는 몇 가지 예제 코드는 다음과 같습니다.
// 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보다 낫습니다. 위의 초기 코드가 작동하지 않는 이유에 대한 설명이 없으면 취할 수있는 방법 일 가능성이 큽니다.
귀하의 원래 코드가 작동하지 않습니다.async
람다는async void
메서드는 완료된 다른 코드에 통보 할 기본 제공 방법이 없습니다. 그래서, 당신이보고있는 것은async void
과async 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.Run
과await
더 자연스럽게 일하다.async
암호.
특히 비동기 작업을 시작할 때 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
}
코드를 다음으로 변경하십시오.
// 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);
Unwrap()
필요한 경우. 창조의async void
람다 (lambda)는 분명히 바람직하지 않지만, 사용하지 않는 부작용 만은 아니다.new Task<Task>(...)
그래서Unwrap()
작동 할 것이다. - Sam Harwellnew Task<Task>
과Unwrap
; 그러나 새로운 코드가 작동합니다.때문에그것은async void
람다. 그리고 그것은 여전히 좋은만큼 좋지 않을 것입니다.Task.Run
기반 솔루션. - Stephen ClearyTask.Run
그리고 원래의 연속. 기존 코드가 작동하지 않는 이유에 대한 통찰력을 주셔서 감사합니다 : 람다가async void
. 물론 그 말을 할 때 모든 것이 더 의미가있었습니다. 그렇다면 나는 왜 그 연속을 사용하고 있는가? 여기에 설명하려고하는 것이 너무 많습니다.하지만 할 일이 많이 있습니다.이 질문. - chue x