2013-01-25 44 views
22

निम्नलिखित को रद्द करने का सही तरीका क्या है?एसिंक ऑपरेशन को रद्द करने का सही तरीका क्या है जो रद्दीकरण टोकन स्वीकार नहीं करता है?

var tcpListener = new TcpListener(connection); 
tcpListener.Start(); 
var client = await tcpListener.AcceptTcpClientAsync(); 

सीधे शब्दों में बुला tcpListener.Stop() एक ObjectDisposedException में परिणाम लगता है और AcceptTcpClientAsync विधि एक CancellationToken संरचना को स्वीकार नहीं करता।

क्या मैं पूरी तरह से कुछ याद कर रहा हूं?

उत्तर

20

मान लें कि आप पर TcpListener class पर कॉल नहीं करना चाहते हैं, तो यहां कोई सही समाधान नहीं है।

आपको सूचित किया जा जब आपरेशन एक निश्चित समय सीमा के भीतर पूरा नहीं करता है, लेकिन मूल कार्य को पूरा करने के लिए अनुमति के साथ ठीक कर रहे हैं, तो आप एक विस्तार विधि बना सकते हैं जिससे की तरह:

public static async Task<T> WithWaitCancellation<T>( 
    this Task<T> task, CancellationToken cancellationToken) 
{ 
    // The tasck completion source. 
    var tcs = new TaskCompletionSource<bool>(); 

    // Register with the cancellation token. 
    using(cancellationToken.Register( 
       s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) 
     // If the task waited on is the cancellation token... 
     if (task != await Task.WhenAny(task, tcs.Task)) 
      throw new OperationCanceledException(cancellationToken); 

    // Wait for one or the other to complete. 
    return await task; 
} 

उपर्युक्त Stephen Toub's blog post "How do I cancel non-cancelable async operations?" से है।

चेतावनी यहाँ दोहरा भालू, यह वास्तव में कार्रवाई को रद्द नहीं करता, क्योंकि AcceptTcpClientAsync method कि एक CancellationToken लेता है की एक अधिभार नहीं है वहाँ है, यह रद्द कर दिया करने में सक्षम नहीं है।

इसका मतलब है कि विस्तार विधि इंगित करता है कि एक रद्द हो किया था, आप मूल Task, नहीं की कॉलबैक पर इंतजार रद्द कर रहे हैं आपरेशन ही रद्द।

इसके लिए यही कारण है कि मैं संकेत मिलता है कि आप रद्द कर रहे हैं इंतजार, न कि वास्तविक कार्रवाई WithWaitCancellation को WithCancellation से विधि नाम दिया गया है।

वहाँ से, यह अपने कोड में उपयोग करने के लिए आसान है:

// Create the listener. 
var tcpListener = new TcpListener(connection); 

// Start. 
tcpListener.Start(); 

// The CancellationToken. 
var cancellationToken = ...; 

// Have to wait on an OperationCanceledException 
// to see if it was cancelled. 
try 
{ 
    // Wait for the client, with the ability to cancel 
    // the *wait*. 
    var client = await tcpListener.AcceptTcpClientAsync(). 
     WithWaitCancellation(cancellationToken); 
} 
catch (AggregateException ae) 
{ 
    // Async exceptions are wrapped in 
    // an AggregateException, so you have to 
    // look here as well. 
} 
catch (OperationCancelledException oce) 
{ 
    // The operation was cancelled, branch 
    // code here. 
} 

नोट आप होगा कि कॉल रैप करने के लिए अपने ग्राहक OperationCanceledException उदाहरण यदि प्रतीक्षा रद्द कर दिया गया फेंक दिया पर कब्जा करने के लिए।

मैंने AggregateException पकड़ में भी फेंक दिया है क्योंकि एसिंक्रोनस ऑपरेशंस से फेंकने पर अपवाद लपेटे जाते हैं (आपको इस मामले में स्वयं के लिए परीक्षण करना चाहिए)।

यह प्रश्न छोड़ देता है कि Stop method (मूल रूप से, जो कुछ भी हो रहा है, चाहे वह क्या हो रहा है, चाहे वह कुछ भी नीचे आंसू हो), जो कि क्या हो रहा है, इस पर निर्भर करता है कि कौन सा दृष्टिकोण बेहतर दृष्टिकोण है। परिस्थितियों।

यदि आप उस संसाधन को साझा नहीं कर रहे हैं जिस पर आप इंतजार कर रहे हैं (इस मामले में, TcpListener), तो संभवत: यह निरस्त विधि को कॉल करने के लिए संसाधनों का बेहतर उपयोग होगा और संचालन से आने वाले किसी भी अपवाद को निगल देगा इंतजार कर रहे हैं (जब आप स्टॉप को कॉल करते हैं तो आपको थोड़ा सा फ्लिप करना होगा और उस ऑपरेशन पर इंतजार कर रहे अन्य क्षेत्रों में उस बिट की निगरानी करें)। यह कोड में कुछ जटिलता जोड़ता है लेकिन यदि आप संसाधन उपयोग के बारे में चिंतित हैं और जितनी जल्दी हो सके सफाई कर रहे हैं, और यह विकल्प आपके लिए उपलब्ध है, तो यह जाने का तरीका है।

संसाधनों के उपयोग नहीं एक मुद्दा है और आप एक अधिक सहकारी तंत्र के साथ सहज महसूस करते हैं, और आप नहीं रहे संसाधन साझा करने, तो का उपयोग कर WithWaitCancellation विधि ठीक है। यहां पेशेवर हैं कि यह क्लीनर कोड है, और बनाए रखने में आसान है।

7

जबकि casperOne के जवाब सही है, वहाँ WithCancellation (या WithWaitCancellation) विस्तार विधि है कि एक ही लक्ष्य को प्राप्त होता है के लिए एक क्लीनर संभावित कार्यान्वयन है: कि क्या जाँच करके

static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken) 
{ 
    return task.IsCompleted 
     ? task 
     : task.ContinueWith(
      completedTask => completedTask.GetAwaiter().GetResult(), 
      cancellationToken, 
      TaskContinuationOptions.ExecuteSynchronously, 
      TaskScheduler.Default); 
} 
  • पहले हम एक तेजी से पथ अनुकूलन है कार्य पहले ही पूरा हो चुका है।
  • फिर हम मूल कार्य को निरंतरता पंजीकृत करते हैं और CancellationToken पैरामीटर पर जाते हैं।
  • निरंतरता मूल कार्य के परिणाम के अर्क (या अपवाद अगर वहाँ एक है) तुल्यकालिक यदि संभव हो तो (TaskContinuationOptions.ExecuteSynchronously) और जब तक रद्द करने के लिए CancellationToken को देख एक ThreadPool धागा का उपयोग कर नहीं तो (TaskScheduler.Default)।

से पहले CancellationToken तो रद्द कर दिया गया लौटे कार्य भंडार परिणाम मूल कार्य को पूरा करता है, तो अन्यथा कार्य को रद्द कर दिया जाता है और जब से प्रतीक्षित एक TaskCancelledException फेंक देते हैं।

+0

आप 'completTask.GetAwaiter() का उपयोग क्यों करते हैं। केवल' पूरा टास्क। परिणाम 'के बजाय GetResult() '? –

+1

@GlennSlayden यह पहले (यानी "असली") अपवाद फेंक देता है यदि 'टास्क। रिसेट' के विपरीत एक है जो 'एग्रीगेट एक्सेप्शन' – i3arnon

+0

फेंकता है ठीक है, बढ़िया; धन्यवाद। क्या आप अपनी सहायक विस्तार विधि की पहली पंक्ति के रूप में 'रद्दीकरण टोकन। थ्रोइफ कैंसेलेशन रिक्वेस्टेड();' जोड़ना भी चाहते हैं? –