Bekleyen bir Görev nasıl iptal edilir?


164

Bu Windows 8 WinRT görevleriyle oynuyorum ve aşağıdaki yöntemi kullanarak bir görevi iptal etmeye çalışıyorum ve bir noktaya kadar çalışıyor. CancelNotification yöntemi çağrılır, bu da görevin iptal edildiğini düşünmenizi sağlar, ancak arka planda görev çalışmaya devam eder, daha sonra tamamlandıktan sonra, Görev durumu her zaman tamamlanır ve asla iptal edilmez. İptal edildiğinde görevi tamamen durdurmanın bir yolu var mı?

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

İptal etmenin çeşitli yollarını anlamama yardımcı olan bu makaleyi buldum .
Uwe Keim

Yanıtlar:


239

Haberlerini okuyun İptali (.NET 4.0 tanıtıldı ve o zamandan beri büyük ölçüde değişmeden olduğu edilmiş) ve Görev Tabanlı Asenkron Modeli nasıl kullanılacağına ilişkin yönergeler sağlamaktadır, CancellationTokenile asyncmetotlarla.

Özetlemek gerekirse, CancellationTokeniptali destekleyen her yönteme bir atarsınız ve bu yöntemin periyodik olarak kontrol etmesi gerekir.

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
Harika bilgi! Bu mükemmel çalıştı, şimdi async yönteminde istisnayı nasıl ele alacağımı anlamaya ihtiyacım var. Teşekkürler dostum! Önerdiğiniz şeyleri okuyacağım.
Carlo

8
Hayır En uzun süren senkron yöntemler var bazı bunları iptal etmek için bir yol - bazen altta yatan bir kaynak kapatmadan veya başka bir yöntemi çağırarak. CancellationToken, özel iptal sistemleriyle birlikte çalışmak için gereken tüm kancalara sahiptir, ancak hiçbir şey dengesiz bir yöntemi iptal edemez.
Stephen Cleary

1
Ah, anlıyorum. ProcessCancelledException'ı yakalamanın en iyi yolu, 'beklemek'i bir deneme / yakalama ile sarmaktır. Bazen AggregatedException alıyorum ve bunu başaramıyorum.
Carlo

3
Sağ. Ben tavsiye Kullanmak asla Waitveya Resultiçinde asyncyöntemlerle; awaitbunun yerine her zaman kullanmalısınız , bu da istisnayı doğru bir şekilde çözer.
Stephen Cleary

11
Sadece merak ediyorum, örneklerin hiçbirinin CancellationToken.IsCancellationRequestedistisna atmamasını ve bunun yerine istisna atmayı önermesinin bir nedeni var mı?
James M

41

Veya değiştirmekten kaçınmak slowFunciçin (örneğin kaynak koduna erişiminizin olmadığını varsayalım):

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

Ayrıca, https://github.com/StephenCleary/AsyncEx adresinden güzel uzantı yöntemleri kullanabilir ve aşağıdaki gibi basit görünmesini sağlayabilirsiniz:

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

1
Oldukça zor görünüyor ... async-bekleyen tüm uygulama gibi. Bu tür yapıların kaynak kodunu daha okunabilir kıldığını düşünmüyorum.
Maxim

1
Teşekkür ederim, bir not - kayıt jetonu daha sonra atılmalıdır, ikinci şey - ConfigureAwaitaksi takdirde UI uygulamalarında zarar verebilirsiniz.
astrowalker

@astrowalker: evet gerçekten jeton kaydı daha iyi kayıt dışı (atılmalıdır). Bu, Register () yöntemine iletilen temsilci içinde, Register () tarafından döndürülen nesne üzerinde dispose çağrısı yapılarak yapılabilir. Ancak "kaynak" jetonu bu durumda yalnızca yerel olduğundan, her şey yine de silinecek ...
sonatique

1
Aslında gereken tek şey onu yerleştirmektir using.
astrowalker

@astrowalker ;-) evet haklısın aslında. Bu durumda bu çok daha basit bir çözümdür! Ancak, Görev.WhenAny doğrudan (beklemeden) dönmek istiyorsanız o zaman başka bir şeye ihtiyacınız var. Bunu söylüyorum çünkü bir keresinde böyle bir yeniden düzenleme sorununa çarptım: kullanmadan önce ... bekle. Daha sonra kod tamamen kırıldığını fark etmeden beklediğimden beri beklediğim (ve fonksiyondaki asenkron) kaldırdım. Ortaya çıkan hatayı bulmak zordu. Dolayısıyla async / await ile birlikte () kullanarak kullanmakta isteksizim. Dispose deseninin async şeyler ile iyi gitmediğini hissediyorum ...
sonatique

15

Karşılanmayan bir durum, bir asenkron yönteminde iptalin nasıl ele alınacağıdır. Örneğin, bir hizmete bazı verileri yüklemeniz ve bir şey hesaplaması için bazı verileri yüklemeniz ve ardından bazı sonuçlar döndürmeniz gereken basit bir durumu ele alalım.

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

İptal işlemini desteklemek istiyorsanız, en kolay yol bir simgeyi iletmek ve her zaman uyumsuz yöntem çağrısı arasında (veya ContinueWith kullanarak) iptal edilip edilmediğini kontrol etmektir. Eğer aramaları çok uzun sürüyor olsa da iptal etmek için bir süre beklemek olabilir. İptal edilir yapılmaz başarısız olmak için küçük bir yardımcı yöntem oluşturdum.

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;
    }
}

Bunu kullanmak .WaitOrCancel(token)için herhangi bir zaman uyumsuz çağrıyı ekleyin :

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;
    }
}

Bunun beklediğiniz Görevi durdurmayacağını ve çalışmaya devam edeceğini unutmayın. Aşağıdaki gibi, bunu durdurmak için farklı bir mekanizma kullanmanız gerekir CancelAsyncörnekte arama ya da daha iyisi aynı geçmek CancellationTokeniçin Tasksonunda iptal işleyebilir böylece. İş parçacığını iptal etmeye çalışmak önerilmez .


1
Bu, görev için beklemeyi iptal ederken, asıl görevi iptal etmediğini unutmayın (örn. UploadDataAsyncArka planda devam edebilir, ancak tamamlandığında, CalculateAsyncbu bölüm zaten beklemeyi durdurduğundan çağrı yapmaz ). Bu, özellikle işlemi yeniden denemek istiyorsanız, sizin için sorunlu olabilir veya olmayabilir. CancellationTokenMümkün olduğunca sonuna kadar geçmek tercih edilen seçenektir.
Miral

1
@Miral doğru, ancak iptal jetonları almayan birçok asenkron yöntem vardır. Örneğin, Async yöntemleriyle bir istemci oluşturduğunuzda iptal belirteçleri içermeyecek olan WCF hizmetlerini ele alalım. Gerçekten de örnekte görüldüğü gibi ve Stephen Cleary'nin de belirttiği gibi, uzun süre çalışan senkronize görevlerin bunları iptal etmenin bir yolu olduğu varsayılmaktadır.
kjbartel

1
Bu yüzden "mümkün olduğunda" dedim. Çoğunlukla bu uyarıdan bahsetmek istedim, böylece daha sonra bu cevabı bulan insanlar yanlış izlenim almazlar.
Miral

@Miral Teşekkürler. Bu uyarıyı yansıtacak şekilde güncellendi.
kjbartel

Ne yazık ki bu 'NetworkStream.WriteAsync' gibi yöntemlerle çalışmaz.
Zeokat

6

Sadece kabul edilmiş cevaba eklemek istiyorum. Bu konuda takılıp kaldım, ancak tüm olayı ele almak için farklı bir rotaya gidiyordum. Çalışmayı beklemek yerine, göreve tamamlanmış bir işleyici ekliyorum.

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

Olay işleyicinin buna benzediği yer

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;
}

Bu rota ile tüm işlemler sizin için zaten yapılır, görev iptal edildiğinde olay işleyiciyi tetikler ve orada iptal edilip edilmediğini görebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.