सी # 4.0 में आग लगाने और भूलने की विधि का सबसे सरल तरीका


100

मुझे वास्तव में यह सवाल पसंद है:

C # में आग लगाने और भूलने का सरल तरीका?

मैं बस यह जानना चाहता हूं कि अब हमारे पास C # 4.0 में पैरेलल एक्सटेंशन हैं। फायर एंड फॉरगेट विथ पैरेलल लाइनक करने के लिए बेहतर क्लीनर तरीका है?


1
उस प्रश्न का उत्तर अभी भी .NET 4.0 पर लागू होता है। आग और भूल जाओ क्यूवेरवर्क्स इटिम से ज्यादा सरल नहीं है।
ब्रायन रासमुसेन

जवाबों:


108

4.0 के लिए उत्तर नहीं है, लेकिन ध्यान देने योग्य है कि .Net 4.5 में आप इसे और भी सरल बना सकते हैं:

#pragma warning disable 4014
Task.Run(() =>
{
    MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014

यह चेतावनी चेतावनी को अक्षम करने के लिए है जो आपको बताती है कि आप इस कार्य को आग के रूप में चला रहे हैं और भूल जाते हैं।

यदि घुंघराले ब्रेसिज़ के अंदर विधि एक कार्य देता है:

#pragma warning disable 4014
Task.Run(async () =>
{
    await MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014

चलो कि नीचे तोड़:

Task.Run एक कार्य देता है, जो एक संकलक चेतावनी (CS4014) चेतावनी देता है कि इस कोड को पृष्ठभूमि में चलाया जाएगा - यह वही है जो आप चाहते थे, इसलिए हम 4014 चेतावनी अक्षम करते हैं।

डिफ़ॉल्ट रूप से, टास्क "मार्शल को मूल थ्रेड पर वापस लाने का प्रयास करता है," जिसका अर्थ है कि यह टास्क पृष्ठभूमि में चलेगा, फिर उस थ्रेड पर लौटने का प्रयास करें जिसने इसे शुरू किया था। मूल थ्रेड हो जाने के बाद अक्सर आग और भूल कार्य समाप्त करें। यह एक ThreadAbortException को फेंकने का कारण होगा। ज्यादातर मामलों में यह हानिरहित है - यह सिर्फ आपको बता रहा है, मैंने फिर से जुड़ने की कोशिश की, मैं असफल रहा, लेकिन आप किसी भी तरह से परवाह नहीं करते हैं। लेकिन यह अभी भी थोडा शोर है उत्पादन में अपने लॉग में या स्थानीय देव में अपने डिबगर में थ्रेडअबोर्ट्स अपवाद हैं। .ConfigureAwait(false)सिर्फ साफ रहने और स्पष्ट रूप से कहने का एक तरीका है, इसे पृष्ठभूमि में चलाएं, और यही है।

चूँकि यह वर्डी है, विशेष रूप से बदसूरत प्रज्ञा, मैं इसके लिए एक पुस्तकालय विधि का उपयोग करता हूं:

public static class TaskHelper
{
    /// <summary>
    /// Runs a TPL Task fire-and-forget style, the right way - in the
    /// background, separate from the current thread, with no risk
    /// of it trying to rejoin the current thread.
    /// </summary>
    public static void RunBg(Func<Task> fn)
    {
        Task.Run(fn).ConfigureAwait(false);
    }

    /// <summary>
    /// Runs a task fire-and-forget style and notifies the TPL that this
    /// will not need a Thread to resume on for a long time, or that there
    /// are multiple gaps in thread use that may be long.
    /// Use for example when talking to a slow webservice.
    /// </summary>
    public static void RunBgLong(Func<Task> fn)
    {
        Task.Factory.StartNew(fn, TaskCreationOptions.LongRunning)
            .ConfigureAwait(false);
    }
}

उपयोग:

TaskHelper.RunBg(async () =>
{
    await doSomethingAsync();
}

7
@ksm आपका दृष्टिकोण दुर्भाग्य से कुछ परेशानी के लिए है - क्या आपने इसका परीक्षण किया है? यह दृष्टिकोण ठीक यही कारण है कि चेतावनी 4014 मौजूद है। बिना प्रतीक्षा किए, और टास्क की मदद के बिना एक एसिंक्स विधि को कॉल करना। और ... उस विधि को चलाने के लिए कारण होगा, हाँ, लेकिन एक बार जब यह खत्म हो जाता है तो इसे वापस मूल थ्रेड में लाने का प्रयास किया जाएगा। अक्सर उस धागे ने पहले ही निष्पादन पूरा कर लिया होगा और आपका कोड भ्रामक, अनिश्चित तरीके से विस्फोट करेगा। यह मत करो! टास्क.रुण का आह्वान "वैश्विक संदर्भ में इसे चलाएं" कहने का एक सुविधाजनक तरीका है, इसके लिए कुछ भी करने की कोशिश नहीं की गई है।
क्रिस मोसची 22

1
@ChrisMoschini मदद करने के लिए खुश है, और अद्यतन के लिए धन्यवाद! अन्य मामले में, जहां मैंने लिखा था "आप इसे एक async गुमनाम कवक के साथ बुला रहे हैं" ऊपर की टिप्पणी में, यह ईमानदारी से मुझे भ्रमित कर रहा है जो सच्चाई है। मुझे पता है कि कोड काम करता है जब कॉलिंग (अनाम फ़ंक्शन) कोड में async शामिल नहीं होता है, लेकिन क्या इसका मतलब यह होगा कि कॉलिंग कोड को एसिंक्रोनस तरीके से नहीं चलाया जाएगा (जो कि खराब है, एक बहुत खराब है)? इसलिए मैं एसिंक्स के बिना सिफारिश नहीं कर रहा हूं , यह अजीब है कि इस मामले में, दोनों काम करते हैं।
निकोलस पीटरसन

8
मैं की बात नहीं दिख रहा है ConfigureAwait(false)पर Task.Runजब तुम नहीं awaitकाम। फ़ंक्शन का उद्देश्य इसके नाम पर है: "कॉन्फ़िगर प्रतीक्षा "। यदि आप कार्य नहीं awaitकरते हैं, तो आप एक निरंतरता को पंजीकृत नहीं करते हैं, और जैसा कि आप कहते हैं, "मूल थ्रेड पर वापस मार्शल" कार्य के लिए कोई कोड नहीं है। बड़ा जोखिम एक गैर-अपवादित अपवाद का होता है जिसे अंतिम रूप देने वाले धागे पर पुनर्भरण किया जाता है, जो इस उत्तर को भी संबोधित नहीं करता है।
माइक स्ट्रोबेल

3
@stricq यहाँ कोई async शून्य उपयोग नहीं हैं। यदि आप async () => ... की बात कर रहे हैं, तो हस्ताक्षर एक फंक है जो एक टास्क देता है, शून्य नहीं।
क्रिस मोशिनकी

2
चेतावनी देने के लिए VB.net पर:#Disable Warning BC42358
Altiano Gerung

85

साथ Taskवर्ग हाँ, लेकिन PLINQ संग्रह से अधिक क्वेरी करने के लिए वास्तव में है।

निम्नलिखित कुछ ऐसा टास्क के साथ करेंगे।

Task.Factory.StartNew(() => FireAway());

या और भी...

Task.Factory.StartNew(FireAway);

या ...

new Task(FireAway).Start();

कहाँ FireAwayहै

public static void FireAway()
{
    // Blah...
}

इसलिए क्लास और मेथड नेम टेरसिटी के आधार पर, यह आपके द्वारा चुने गए चयन के आधार पर छह और उन्नीस अक्षरों के बीच थ्रेडपूल संस्करण को धड़कता है :)

ThreadPool.QueueUserWorkItem(o => FireAway());

हालांकि वे कार्यक्षमता के बराबर नहीं हैं?
जोनाथन क्रेसनर

6
StartNew और नए टास्क के बीच एक सूक्ष्म अर्थ अंतर होता है। लेकिन अन्यथा, हाँ। वे सभी फायरएवे को थ्रेड में एक धागे पर चलाने के लिए कतार लगाते हैं।
एडी मिलर

इस मामले के लिए कार्य करना: fire and forget in ASP.NET WebForms and windows.close()?
प्रीग्टनटनकोजेरो कैब्रोन

32

मेरे पास इस मुद्दे के प्रमुख उत्तर के साथ कुछ मुद्दे हैं।

सबसे पहले, एक सच्ची आग और भूलने की स्थिति में, आप शायद awaitकाम नहीं करेंगे , इसलिए इसे जोड़ना बेकार है ConfigureAwait(false)। यदि आप awaitद्वारा लौटाया गया मान नहीं है ConfigureAwait, तो संभवतः इसका कोई प्रभाव नहीं हो सकता है।

दूसरा, आपको इस बात से अवगत होना चाहिए कि जब कार्य अपवाद के साथ पूरा होता है तो क्या होता है। उस सरल समाधान पर विचार करें जो @ ade-miller ने सुझाया है:

Task.Factory.StartNew(SomeMethod);  // .NET 4.0
Task.Run(SomeMethod);               // .NET 4.5

यह एक खतरे का परिचय देता है: यदि कोई अखंड अपवाद इससे बच जाता है SomeMethod(), तो वह अपवाद कभी भी नहीं देखा जाएगा, और 1 आपके थ्रेड को क्रैश करते हुए, अंतिम थ्रेड पर पुन: फेंका जा सकता है। इसलिए मैं यह सुनिश्चित करने के लिए एक सहायक विधि का उपयोग करने की सलाह दूंगा कि किसी भी परिणामी अपवाद को देखा जाए।

आप कुछ इस तरह लिख सकते हैं:

public static class Blindly
{
    private static readonly Action<Task> DefaultErrorContinuation =
        t =>
        {
            try { t.Wait(); }
            catch {}
        };

    public static void Run(Action action, Action<Exception> handler = null)
    {
        if (action == null)
            throw new ArgumentNullException(nameof(action));

        var task = Task.Run(action);  // Adapt as necessary for .NET 4.0.

        if (handler == null)
        {
            task.ContinueWith(
                DefaultErrorContinuation,
                TaskContinuationOptions.ExecuteSynchronously |
                TaskContinuationOptions.OnlyOnFaulted);
        }
        else
        {
            task.ContinueWith(
                t => handler(t.Exception.GetBaseException()),
                TaskContinuationOptions.ExecuteSynchronously |
                TaskContinuationOptions.OnlyOnFaulted);
        }
    }
}

इस कार्यान्वयन में कम से कम ओवरहेड होना चाहिए: कार्य केवल सफलतापूर्वक पूरा नहीं होने पर निरंतरता को लागू किया जाता है, और इसे सिंक्रोनाइज़ किया जाना चाहिए (मूल कार्य से अलग शेड्यूल किए जाने के विपरीत)। "आलसी" मामले में, आप निरंतरता प्रतिनिधि के लिए आवंटन भी नहीं लेंगे।

एक अतुल्यकालिक ऑपरेशन को मारना तब तुच्छ हो जाता है:

Blindly.Run(SomeMethod);                              // Ignore error
Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e));  // Log error

1. यह .NET 4.0 में डिफ़ॉल्ट व्यवहार था। .NET 4.5 में, डिफ़ॉल्ट व्यवहार को इस तरह से बदल दिया गया था कि अंतिम अपवाद पर अप्रार्थित अपवादों को फिर से नहीं हटाया जाएगा (हालाँकि आप अभी भी उन्हें टास्कस्क्राइडर पर UnobservedTaskException घटना के माध्यम से देख सकते हैं)। हालाँकि, डिफ़ॉल्ट कॉन्फ़िगरेशन ओवरराइड किया जा सकता है, और यहां तक ​​कि अगर आपके एप्लिकेशन को .NET 4.5 की आवश्यकता है, तो आपको यह नहीं मान लेना चाहिए कि अप्रयुक्त कार्य अपवाद हानिरहित होंगे।


1
यदि किसी हैंडलर को पारित ContinueWithकिया जाता है और रद्दीकरण के कारण लागू किया गया था, तो पूर्ववर्ती अपवाद की संपत्ति शून्य होगी और एक शून्य संदर्भ अपवाद फेंक दिया जाएगा। कार्य निरंतरता विकल्प को सेट OnlyOnFaultedकरने के लिए अशक्त जांच या जांच की आवश्यकता को समाप्त कर सकता है यदि अपवाद का उपयोग करने से पहले यह शून्य है।
sanmcp

विभिन्न विधि हस्ताक्षरों के लिए रन () विधि को ओवरराइड करने की आवश्यकता है। टास्क के विस्तार विधि के रूप में ऐसा करने के लिए बेहतर है।
stricq

1
@stricq सवाल "आग और भूल जाओ" ऑपरेशन के बारे में पूछा गया (यानी, स्थिति की कभी जाँच नहीं की गई और परिणाम कभी नहीं देखा गया), इसीलिए मैंने इस पर ध्यान केंद्रित किया। जब सवाल यह है कि पैर में सबसे ज्यादा साफ-सफाई कैसे की जाती है, तो इस बात पर बहस करना कि कौन सा समाधान डिजाइन के नजरिए से "बेहतर" है, बल्कि लूट जैसा हो जाता है :)। सबसे अच्छा उत्तर यकीनन "नहीं" है, लेकिन यह न्यूनतम उत्तर की लंबाई से कम है, इसलिए मैंने एक संक्षिप्त समाधान प्रदान करने पर ध्यान केंद्रित किया जो अन्य उत्तरों में मौजूद मुद्दों से बचा जाता है। तर्क आसानी से एक बंद में लिपटे हुए हैं, और कोई वापसी मूल्य के साथ, Taskएक कार्यान्वयन विवरण बन जाता है।
माइक स्ट्रोबेल

@stricq ने कहा, मैं आपसे असहमत नहीं हूं। आपके द्वारा प्रस्तावित विकल्प ठीक वही है जो मैंने अतीत में किया है, अलग-अलग कारणों से। "जब एक डेवलपर किसी दोषपूर्ण का निरीक्षण करने में विफल रहता है, तो मैं ऐप को क्रैश करने से बचना चाहता हूं Task" यह एक समान नहीं है "मुझे पृष्ठभूमि ऑपरेशन को किक करने का एक सरल तरीका चाहिए, इसके परिणाम का कभी भी निरीक्षण न करें, और मुझे परवाह नहीं है कि क्या तंत्र का उपयोग किया जाता है इसे करने के लिए।" उस आधार पर, मैं पूछे गए प्रश्न के अपने उत्तर के पीछे खड़ा हूं ।
माइक स्ट्रोबेल

इस मामले के लिए कार्य करना: fire and forgetमें ASP.NET WebForms और windows.close()?
प्रीग्टनटनकोजेरो कैब्रोन

7

माइक स्ट्रोबेल के जवाब के साथ होने वाले कुछ मुद्दे को ठीक करने के लिए:

यदि आप उपयोग करते हैं var task = Task.Run(action)और उस कार्य को जारी रखने के बाद असाइन करते हैं, तो आप Taskअपवाद हैंडलर निरंतरता को असाइन करने से पहले कुछ अपवाद फेंकने के जोखिम में चलेंगे Task। तो, नीचे का वर्ग इस जोखिम से मुक्त होना चाहिए:

using System;
using System.Threading.Tasks;

namespace MyNameSpace
{
    public sealed class AsyncManager : IAsyncManager
    {
        private Action<Task> DefaultExeptionHandler = t =>
        {
            try { t.Wait(); }
            catch { /* Swallow the exception */ }
        };

        public Task Run(Action action, Action<Exception> exceptionHandler = null)
        {
            if (action == null) { throw new ArgumentNullException(nameof(action)); }

            var task = new Task(action);

            Action<Task> handler = exceptionHandler != null ?
                new Action<Task>(t => exceptionHandler(t.Exception.GetBaseException())) :
                DefaultExeptionHandler;

            var continuation = task.ContinueWith(handler,
                TaskContinuationOptions.ExecuteSynchronously
                | TaskContinuationOptions.OnlyOnFaulted);
            task.Start();

            return continuation;
        }
    }
}

यहां, taskइसे सीधे नहीं चलाया जाता है, इसके बजाय इसे बनाया जाता है, एक निरंतरता असाइन की जाती है, और उसके बाद ही कार्य को पूरा किया जाता है ताकि कार्य जारी करने से पहले कार्य को पूरा करने (या कुछ अपवाद को फेंकने) के जोखिम को खत्म किया जा सके।

यहाँ Runविधि निरंतरता लौटाती है Taskइसलिए मैं यह सुनिश्चित करने के लिए इकाई परीक्षण लिखने में सक्षम हूं कि निष्पादन पूर्ण है। आप इसे अपने उपयोग में सुरक्षित रूप से अनदेखा कर सकते हैं।


क्या यह जानबूझकर है कि आपने इसे एक स्थिर वर्ग नहीं बनाया है?
डीनट्वो

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