इंतजार में टास्क कैसे रद्द करें?


164

मैं इन विंडोज 8 WinRT कार्यों के साथ खेल रहा हूं, और मैं नीचे दी गई विधि का उपयोग करके किसी कार्य को रद्द करने की कोशिश कर रहा हूं, और यह कुछ बिंदु पर काम करता है। रद्दकरण विधि DOES कहलाती है, जिससे आपको लगता है कि कार्य रद्द कर दिया गया था, लेकिन पृष्ठभूमि में कार्य चलता रहता है, फिर उसके पूरा होने के बाद, टास्क की स्थिति हमेशा पूरी हो जाती है और कभी रद्द नहीं होती है। क्या यह रद्द होने पर कार्य को पूरी तरह से रोकने का कोई तरीका है?

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

बस यह लेख मिला जिसने मुझे रद्द करने के विभिन्न तरीकों को समझने में मदद की।
उवे कीम

जवाबों:


239

रद्दीकरण पर पढ़ें (जो .NET 4.0 में पेश किया गया था और तब से काफी हद तक अपरिवर्तित है) और टास्क-आधारित एसिंक्रोनस पैटर्न , जो कि तरीकों के CancellationTokenसाथ उपयोग करने के बारे में दिशानिर्देश प्रदान करता है async

संक्षेप में, आप CancellationTokenप्रत्येक विधि को रद्द करने का समर्थन करते हैं, और यह विधि समय-समय पर जाँच करनी चाहिए।

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
वाह बढ़िया जानकारी! यह पूरी तरह से काम करता है, अब मुझे यह पता लगाने की आवश्यकता है कि एसिंक्स विधि में अपवाद को कैसे संभालना है। धन्यवाद दोस्त! आपके द्वारा सुझाए गए सामान को पढ़ूंगा।
कार्लो

8
नहीं। अधिकांश लंबे समय तक चलने वाले तुल्यकालिक तरीकों में उन्हें रद्द करने का कोई तरीका है - कभी-कभी किसी अंतर्निहित संसाधन को बंद करके या किसी अन्य विधि को कॉल करके। CancellationTokenकस्टम रद्दीकरण प्रणाली के साथ इंटरोप करने के लिए सभी हुक आवश्यक हैं, लेकिन एक अचूक विधि को रद्द नहीं कर सकते हैं।
स्टीफन क्लीयर

1
ओह समझा। तो सबसे अच्छा तरीका है ProcessCancelledException को पकड़ने का एक प्रयास / कैच में 'वेट' को लपेटकर? कभी-कभी मुझे एग्रीगेटेड एक्ससेप्शन मिलता है और मैं उसे नहीं संभाल सकता।
कार्लो

3
सही। मेरा सुझाव है कि आप कभी भी Waitया विधियों Resultमें उपयोग न करें async; आपको हमेशा awaitइसके बजाय उपयोग करना चाहिए , जो अपवाद को सही ढंग से उजागर नहीं करता है।
स्टीफन क्लीयर

11
बस जिज्ञासु, क्या कोई कारण है कि कोई भी उदाहरण का उपयोग नहीं करता है CancellationToken.IsCancellationRequestedऔर इसके बजाय अपवादों को फेंकने का सुझाव देता है?
जेम्स एम

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
यह बहुत मुश्किल लग रहा है ... पूरे async-प्रतीक्षा कार्यान्वयन के रूप में। मुझे नहीं लगता कि इस तरह के निर्माण स्रोत कोड को अधिक पठनीय बनाते हैं।
मैक्सिम

1
धन्यवाद, एक नोट - पंजीकरण टोकन बाद में निपटाया जाना चाहिए, दूसरी बात - ConfigureAwaitयूआई ऐप्स में चोट लग सकती है।
एस्ट्रोवैलर

@astrowalker: हाँ वास्तव में टोकन का पंजीकरण बेहतर अपंजीकृत (निपटारा) होगा। यह उस प्रतिनिधि के अंदर किया जा सकता है जिसे रजिस्टर () द्वारा उस ऑब्जेक्ट पर डिस्पोज करके कॉल किया जाता है जिसे रजिस्टर () द्वारा लौटाया जाता है। हालाँकि "स्रोत" टोकन इस मामले में केवल स्थानीय है, वैसे भी सब कुछ साफ हो जाएगा ...
sonatique

1
वास्तव में यह सब लेता है यह में घोंसला है using
ज्योतिषी

@astrowalker ;-) हाँ आप वास्तव में सही हैं। इस मामले में यह बहुत सरल उपाय है! हालाँकि, यदि आप टास्क को वापस करना चाहते हैं।जब सीधे (बिना इंतजार किए) फिर आपको कुछ और चाहिए। मैं यह कहता हूं क्योंकि मैंने एक बार इस तरह के एक refactoring मुद्दे को मारा था: इससे पहले कि मैंने उपयोग किया था ... प्रतीक्षा करें। मैंने तब प्रतीक्षित (और फ़ंक्शन पर async) को हटा दिया क्योंकि यह एकमात्र था, बिना यह ध्यान दिए कि मैं कोड को पूरी तरह से तोड़ रहा था। परिणामस्वरूप बग को ढूंढना मुश्किल था। मैं इस प्रकार async / प्रतीक्षा के साथ एक साथ उपयोग () का उपयोग करने के लिए अनिच्छुक हूं। मुझे लगता है कि डिस्पोज़ पैटर्न वैसे भी async चीजों के साथ अच्छी तरह से नहीं चलता है ...
सोनाटिक

15

एक मामला जो कवर नहीं किया गया है वह यह है कि एक async विधि के अंदर रद्दीकरण को कैसे संभालना है। उदाहरण के लिए एक साधारण मामले को लें, जहाँ आपको किसी डेटा को किसी सेवा में अपलोड करने की आवश्यकता हो, उसे कुछ गणना करें और फिर कुछ परिणाम लौटाएं।

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

यदि आप रद्दीकरण का समर्थन करना चाहते हैं तो सबसे आसान तरीका एक टोकन को पास करना होगा और जांचना होगा कि क्या यह प्रत्येक async विधि कॉल (या 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)किसी भी async कॉल में जोड़ें :

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जब संभव हो तो सभी तरह से पास करना पसंदीदा विकल्प है।
मिरल

1
@ मिरल सच है, हालांकि कई एस्कॉन तरीके हैं जो रद्द टोकन नहीं लेते हैं। उदाहरण के लिए WCF सेवाओं को लें, जो जब आप Async विधियों के साथ क्लाइंट उत्पन्न करते हैं तो रद्दीकरण टोकन शामिल नहीं होंगे। वास्तव में जैसा कि उदाहरण से पता चलता है, और जैसा कि स्टीफन क्लीरी ने भी उल्लेख किया है, यह माना जाता है कि लंबे समय तक चलने वाले तुल्यकालिक कार्यों को रद्द करने का कोई तरीका है।
kjbartel

1
इसलिए मैंने कहा "जब संभव हो"। ज्यादातर मैं सिर्फ इस चेतावनी का उल्लेख करना चाहता था ताकि बाद में इस जवाब को खोजने वाले लोगों को गलत धारणा न मिले।
मिरल

@ मिरल धन्यवाद। मैंने इस चेतावनी को प्रतिबिंबित करने के लिए अद्यतन किया है।
kjbartel 10

अफसोस की बात है कि यह 'NetworkStream.WriteAsync' जैसे तरीकों से काम नहीं करता है।
ज़ायोकैट

6

मैं केवल पहले से ही स्वीकृत उत्तर को जोड़ना चाहता हूं। मैं इस पर अटक गया था, लेकिन मैं पूरी घटना को संभालने पर एक अलग मार्ग जा रहा था। प्रतीक्षा करने के बजाय, मैं कार्य में एक पूर्ण हैंडलर जोड़ता हूं।

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

इस मार्ग के साथ, सभी हैंडलिंग पहले से ही आपके लिए की जाती है, जब कार्य रद्द हो जाता है तो यह केवल इवेंट हैंडलर को ट्रिगर करता है और आप देख सकते हैं कि क्या इसे रद्द कर दिया गया था।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.