대기중인 작업을 취소하는 방법?


164

이 Windows 8 WinRT 작업을하고 있는데 아래 방법을 사용하여 작업을 취소하려고하는데 어느 정도 작동합니다. CancelNotification 메소드가 호출되어 작업이 취소되었다고 생각하지만 백그라운드에서 작업이 계속 실행되고 완료된 후에는 작업 상태가 항상 완료되고 절대 취소되지 않습니다. 작업이 취소 될 때 작업을 완전히 중단 할 수있는 방법이 있습니까?

private async void TryTask()
{
    CancellationTokenSource source = new CancellationTokenSource();
    source.Token.Register(CancelNotification);
    source.CancelAfter(TimeSpan.FromSeconds(1));
    var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token);

    await task;            

    if (task.IsCompleted)
    {
        MessageDialog md = new MessageDialog(task.Result.ToString());
        await md.ShowAsync();
    }
    else
    {
        MessageDialog md = new MessageDialog("Uncompleted");
        await md.ShowAsync();
    }
}

private int slowFunc(int a, int b)
{
    string someString = string.Empty;
    for (int i = 0; i < 200000; i++)
    {
        someString += "a";
    }

    return a + b;
}

private void CancelNotification()
{
}

방금 취소하는 다양한 방법을 이해하는 데 도움 되는 이 기사 를 찾았 습니다.
Uwe Keim

답변:


239

읽기 최대 취소 (.NET 4.0에 도입 된 이후 큰 변화가되었습니다)와 작업 기반 비동기 패턴 사용하는 방법에 대한 지침을 제공하고, CancellationToken함께 async방법을.

요약하면, CancellationToken취소를 지원하는 각 메소드에 a 를 전달하면 해당 메소드가 주기적으로 확인해야합니다.

private async Task TryTask()
{
  CancellationTokenSource source = new CancellationTokenSource();
  source.CancelAfter(TimeSpan.FromSeconds(1));
  Task<int> task = Task.Run(() => slowFunc(1, 2, source.Token), source.Token);

  // (A canceled task will raise an exception when awaited).
  await task;
}

private int slowFunc(int a, int b, CancellationToken cancellationToken)
{
  string someString = string.Empty;
  for (int i = 0; i < 200000; i++)
  {
    someString += "a";
    if (i % 1000 == 0)
      cancellationToken.ThrowIfCancellationRequested();
  }

  return a + b;
}

2
와우 좋은 정보! 그것은 완벽하게 작동했습니다. 이제 비동기 메소드에서 예외를 처리하는 방법을 알아야합니다. 고마워요! 제안한 내용을 읽겠습니다.
Carlo

8
아니 대부분의 장기 실행 동기 방법이 일부 를 취소 할 수있는 방법을 - 때로는 기본 자원을 닫거나 다른 방법을 호출하여. CancellationToken사용자 지정 취소 시스템과 상호 작용하는 데 필요한 모든 후크가 있지만 취소 할 수없는 방법을 취소 할 수는 없습니다.
Stephen Cleary

1
아 알 겠어요 따라서 ProcessCancelledException을 잡는 가장 좋은 방법은 'await'를 try / catch로 감싸는 것입니다. 때로는 AggregatedException이 발생하여 처리 할 수 ​​없습니다.
Carlo

3
권리. 나는 추천 당신이 사용하지 않을 것을 WaitResultasync방법; 항상 await예외를 올바르게 풀어주는 대신 대신 사용해야 합니다.
Stephen Cleary

11
궁금한 점이 있는데, 예제 중 어느 것도 사용 CancellationToken.IsCancellationRequested하지 않고 예외를 던지는 것을 제안 하는 이유가 있습니까?
James M

41

또는 수정을 피하려면 slowFunc(예를 들어 소스 코드에 액세스 할 수 없음) :

var source = new CancellationTokenSource(); //original code
source.Token.Register(CancelNotification); //original code
source.CancelAfter(TimeSpan.FromSeconds(1)); //original code
var completionSource = new TaskCompletionSource<object>(); //New code
source.Token.Register(() => completionSource.TrySetCanceled()); //New code
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token); //original code

//original code: await task;  
await Task.WhenAny(task, completionSource.Task); //New code

https://github.com/StephenCleary/AsyncEx 에서 멋진 확장 방법을 사용 하여 다음과 같이 간단하게 만들 수 있습니다.

await Task.WhenAny(task, source.Token.AsTask());

1
전체 비동기 대기 구현으로 매우 까다로워 보입니다. 그러한 구성으로 인해 소스 코드를 더 읽기 쉽다고 생각하지 않습니다.
Maxim

1
감사합니다. 토큰 등록은 나중에 처리해야합니다. 두 번째 ConfigureAwait는 UI 앱에서 다칠 수 있습니다.
astrowalker

@astrowalker : 예, 실제로 토큰 등록은 등록 취소 (폐기)하는 것이 좋습니다. 이것은 Register ()에 의해 반환 된 객체에 대해 dispose를 호출하여 Register ()에 전달 된 대리자 내에서 수행 할 수 있습니다. 그러나이 경우 "소스"토큰은 로컬이므로 모든 것이 지워질 것입니다.
sonatique

1
실제로 필요한 것은에 중첩하는 것입니다 using.
astrowalker

@astrowalker ;-) 네 맞아요. 이 경우 훨씬 간단한 솔루션입니다! 그러나 Task.WhenAny를 직접 반환하려면 (기다리지 않고) 다른 것이 필요합니다. 나는 이것을 사용하기 전에 한 번 이렇게 리팩토링 문제를 겪었 기 때문에 이것을 말한다. 그런 다음 await (및 함수의 비동기)가 코드를 완전히 깨뜨린 것을 알지 못하고 유일한 것이기 때문에 제거했습니다. 결과 버그를 찾기가 어려웠습니다. 따라서 async / await과 함께 using ()을 사용하는 것을 꺼려합니다. 나는 Dispose 패턴이 어쨌든 비동기적인 것들과 잘
어울리지 않는다고

15

다루지 않은 한 가지 경우는 비동기 메서드 내에서 취소를 처리하는 방법입니다. 예를 들어, 서비스에 일부 데이터를 업로드하여 무언가를 계산 한 다음 결과를 반환해야하는 간단한 경우를 생각해보십시오.

public async Task<Results> ProcessDataAsync(MyData data)
{
    var client = await GetClientAsync();
    await client.UploadDataAsync(data);
    await client.CalculateAsync();
    return await client.GetResultsAsync();
}

취소를 지원하려면 가장 쉬운 방법은 토큰을 전달하고 각 비동기 메서드 호출간에 (또는 ContinueWith를 사용하여) 취소되었는지 확인하는 것입니다. 통화가 너무 길면 취소하기 위해 잠시 기다릴 수 있습니다. 취소되는 즉시 실패하는 작은 도우미 메서드를 만들었습니다.

public static class TaskExtensions
{
    public static async Task<T> WaitOrCancel<T>(this Task<T> task, CancellationToken token)
    {
        token.ThrowIfCancellationRequested();
        await Task.WhenAny(task, token.WhenCanceled());
        token.ThrowIfCancellationRequested();

        return await task;
    }

    public static Task WhenCanceled(this CancellationToken cancellationToken)
    {
        var tcs = new TaskCompletionSource<bool>();
        cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
        return tcs.Task;
    }
}

따라서 그것을 사용 .WaitOrCancel(token)하려면 모든 비동기 호출에 추가 하십시오.

public async Task<Results> ProcessDataAsync(MyData data, CancellationToken token)
{
    Client client;
    try
    {
        client = await GetClientAsync().WaitOrCancel(token);
        await client.UploadDataAsync(data).WaitOrCancel(token);
        await client.CalculateAsync().WaitOrCancel(token);
        return await client.GetResultsAsync().WaitOrCancel(token);
    }
    catch (OperationCanceledException)
    {
        if (client != null)
            await client.CancelAsync();
        throw;
    }
}

이렇게해도 대기중인 작업이 중지되지 않고 계속 실행됩니다. CancelAsync예제 의 호출 과 같은 다른 메커니즘을 사용하여 중단 을 처리하거나 최종적으로 취소를 처리 할 수 ​​있도록 동일 CancellationToken하게 전달 Task해야합니다. 스레드 중단을 시도 하지 않는 것이 좋습니다 .


1
이렇게하면 작업을 기다리는 동안 취소되지만 실제 작업은 취소되지 않습니다 (예 : UploadDataAsync백그라운드에서 계속 될 수 있지만 완료되면 CalculateAsync해당 부분이 이미 대기를 중단했기 때문에 전화를 걸지 않습니다 ). 특히 작업을 재 시도하려는 경우 문제가 될 수도 있고 아닐 수도 있습니다. 가능한 한 CancellationToken완전히 통과하는 것이 선호되는 옵션입니다.
Miral

1
@Miral 사실이지만 취소 토큰을 사용하지 않는 많은 비동기 메소드가 있습니다. 비동기 메소드를 사용하여 클라이언트를 생성 할 때 취소 토큰을 포함하지 않는 WCF 서비스를 예로들 수 있습니다. 실제로 예제에서 알 수 있듯이 Stephen Cleary도 언급했듯이 오래 실행되는 동기 작업은 취소 할 수있는 방법이 있다고 가정합니다.
kjbartel '

1
그렇기 때문에 "가능한 경우"라고 말했습니다. 대부분이 경고를 나중에 사람들이 잘못된 인상을 얻지 못하도록 언급하기를 원했습니다.
Miral

@Miral 감사합니다. 이 경고를 반영하여 업데이트했습니다.
kjbartel

슬프게도 이것은 'NetworkStream.WriteAsync'와 같은 메소드에서는 작동하지 않습니다.
Zeokat

6

이미 수락 된 답변에 추가하고 싶습니다. 나는 이것에 붙어 있었지만 완전한 이벤트를 처리하기 위해 다른 길을 가고 있었다. Await을 실행하는 대신 완성 된 처리기를 작업에 추가합니다.

Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);

이벤트 핸들러는 다음과 같습니다

private void CommentLoadComplete(IAsyncAction sender, AsyncStatus status )
{
    if (status == AsyncStatus.Canceled)
    {
        return;
    }
    CommentsItemsControl.ItemsSource = Comments.Result;
    CommentScrollViewer.ScrollToVerticalOffset(0);
    CommentScrollViewer.Visibility = Visibility.Visible;
    CommentProgressRing.Visibility = Visibility.Collapsed;
}

이 라우트를 사용하면 모든 처리가 이미 완료되었으며, 작업이 취소되면 이벤트 핸들러 만 트리거하고 거기서 취소되었는지 확인할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.