[C#] 대기중인 작업을 취소하는 방법?

이 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()
{
}



답변

읽기 최대 취소 (.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;
}


답변

또는 수정을 피하려면 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());


답변

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

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해야합니다. 스레드 중단을 시도 하지 않는 것이 좋습니다 .


답변

이미 수락 된 답변에 추가하고 싶습니다. 나는 이것에 붙어 있었지만 완전한 이벤트를 처리하기 위해 다른 길을 가고 있었다. 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;
}

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


답변