TaskCompletionSource <T> का उपयोग कब किया जाना चाहिए?


199

AFAIK, यह सब जानता है कि कुछ बिंदु पर, इसकी संपत्ति के माध्यम से इसका पूरा करने के लिए इसकी विधि SetResultया SetExceptionविधि कहा जा रहा है।Task<T>Task

दूसरे शब्दों में, यह एक Task<TResult>और इसके पूरा होने के लिए निर्माता के रूप में कार्य करता है ।

मैंने यहाँ उदाहरण देखा :

अगर मुझे एसिंक्रोनस रूप से एक फंक को निष्पादित करने का एक तरीका चाहिए और उस ऑपरेशन का प्रतिनिधित्व करने के लिए एक टास्क है।

public static Task<T> RunAsync<T>(Func<T> function) 
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        {  
            T result = function(); 
            tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
}

जिसका उपयोग किया जा सकता है * यदि मेरे पास नहीं है Task.Factory.StartNew- लेकिन मेरे पास है Task.Factory.StartNew

सवाल:

किसी उदाहरण से संबंधित एक परिदृश्य कृपया उसका वर्णन सीधे करने के लिए TaskCompletionSource एक के लिए नहीं है और काल्पनिक है, जिसमें मेरे पास नहीं है स्थिति Task.Factory.StartNew?


5
TaskCompletionSource मुख्य रूप से नए थ्रेड्स बनाने के बिना टास्क के साथ async एपीआई आधारित इवेंट रैपिंग के लिए उपयोग किया जाता है।
अरविस

जवाबों:


230

मैं ज्यादातर इसका उपयोग तब करता हूं जब केवल एक ईवेंट आधारित एपीआई उपलब्ध हो ( उदाहरण के लिए विंडोज फोन 8 सॉकेट ):

public Task<Args> SomeApiWrapper()
{
    TaskCompletionSource<Args> tcs = new TaskCompletionSource<Args>(); 

    var obj = new SomeApi();

    // will get raised, when the work is done
    obj.Done += (args) => 
    {
        // this will notify the caller 
        // of the SomeApiWrapper that 
        // the task just completed
        tcs.SetResult(args);
    }

    // start the work
    obj.Do();

    return tcs.Task;
}

इसलिए यह विशेष रूप से उपयोगी है जब C # 5 asyncकीवर्ड के साथ प्रयोग किया जाता है ।


4
क्या आप शब्दों में लिख सकते हैं कि हम यहाँ क्या देखते हैं? क्या ऐसा है कि SomeApiWrapperकहीं पर इंतजार किया जा रहा है, जब तक प्रकाशक उस घटना को नहीं उठाते हैं जो इस कार्य को पूरा करने का कारण बनती है?
रॉय नमिर

मेरे द्वारा जोड़े गए लिंक पर एक नज़र डालें
GameScripting

6
बस एक अपडेट, Microsoft ने Microsoft.Bcl.AsyncNuGet पर पैकेज जारी किया है जो async/await.NET 4.0 परियोजनाओं (VS2012 और उच्चतर अनुशंसित है) में कीवर्ड की अनुमति देता है ।
एरिक

1
@ Fran_gg7 आप एक कैंसेलेशनटोकन का उपयोग कर सकते हैं, देखें msdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspx या स्टैकओवरफ्लो पर यहां एक नए प्रश्न के रूप में
GameScripting

1
इस कार्यान्वयन के साथ समस्या यह है कि यह एक मेमोरी रिसाव उत्पन्न करता है क्योंकि घटना कभी भी obj.Done से जारी नहीं होती है
वाल्टर वेव्सोवेन

78

मेरे अनुभवों में, TaskCompletionSourceपुराने अतुल्यकालिक पैटर्न को आधुनिक async/awaitपैटर्न में लपेटने के लिए बहुत अच्छा है ।

सबसे अधिक लाभकारी उदाहरण मैं सोच सकता हूं कि कब किसके साथ काम करना है Socket। इसमें पुराने एपीएम और ईएपी पैटर्न हैं, लेकिन वे awaitable Taskतरीके नहीं हैं जो हैं TcpListenerऔर TcpClientहैं।

मैं व्यक्तिगत रूप से NetworkStreamवर्ग के साथ कई मुद्दे रखता हूं और कच्चे को पसंद करता हूं Socket। यह होने के नाते कि मैं भी async/awaitपैटर्न से प्यार करता हूं, मैंने एक विस्तार वर्ग बनाया SocketExtenderजो कि कई विस्तार विधियां बनाता है Socket

ये सभी विधियाँ TaskCompletionSource<T>अतुल्यकालिक कॉल को लपेटने के लिए उपयोग करती हैं जैसे:

    public static Task<Socket> AcceptAsync(this Socket socket)
    {
        if (socket == null)
            throw new ArgumentNullException("socket");

        var tcs = new TaskCompletionSource<Socket>();

        socket.BeginAccept(asyncResult =>
        {
            try
            {
                var s = asyncResult.AsyncState as Socket;
                var client = s.EndAccept(asyncResult);

                tcs.SetResult(client);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }

        }, socket);

        return tcs.Task;
    }

मैं विधियों socketमें पास करता हूं BeginAcceptताकि मुझे स्थानीय पैरामीटर को फहराने के लिए संकलक से थोड़ा सा प्रदर्शन बूस्ट न मिले।

फिर यह सब की सुंदरता:

 var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 listener.Bind(new IPEndPoint(IPAddress.Loopback, 2610));
 listener.Listen(10);

 var client = await listener.AcceptAsync();

1
टास्क क्यों होगा।
टोला ओडजयी

23
@ टोला जैसा कि एक थ्रेडपूल थ्रेड पर चलने वाला एक नया कार्य बनाया गया होगा, लेकिन ऊपर दिए गए कोड का उपयोग I / o पूरा करने वाले धागे से शुरू होता है, शुरुआत: iow: यह एक नया थ्रेड शुरू नहीं करता है।
फ्रान्स बोउमा

4
धन्यवाद, @ फ्रान्स-बुमा। तो TaskCompletionSource कोड को परिवर्तित करने का एक आसान तरीका है जो शुरुआत ... End ... कथनों को एक कार्य में उपयोग करता है?
टोला ओडजायि

3
@TolaOdejayi एक देर से उत्तर के बिट, लेकिन हाँ जो इसके लिए मुझे मिले प्राथमिक उपयोग मामलों में से एक है। यह कोड के इस संक्रमण के लिए आश्चर्यजनक रूप से काम करता है।
एरिक

4
को देखो TaskFactory <TResult> .FromAsync रैप करने के लिए Begin.. End...बयान।
माइक्रोबिग

37

मेरे लिए, उपयोग करने के लिए एक क्लासिक परिदृश्य TaskCompletionSourceयह है कि यह संभव है कि मेरी विधि को एक समय लेने वाला ऑपरेशन करना जरूरी नहीं होगा । यह हमें क्या करने की अनुमति देता है उन विशिष्ट मामलों को चुनना है जहां हम एक नए धागे का उपयोग करना चाहते हैं।

इसके लिए एक अच्छा उदाहरण है जब आप कैश का उपयोग करते हैं। आपके पास एक GetResourceAsyncविधि हो सकती है , जो अनुरोध किए गए संसाधन के लिए कैश में दिखती है और TaskCompletionSourceयदि संसाधन पाया गया था तो एक बार में (नए धागे का उपयोग किए बिना, उपयोग करके )। केवल यदि संसाधन नहीं मिला, तो हम एक नए थ्रेड का उपयोग करना चाहते हैं और इसका उपयोग करना चाहते हैं Task.Run()

एक कोड उदाहरण यहां देखा जा सकता है: सशर्त रूप से कार्यों का उपयोग करके कोड को सशर्त रूप से कैसे चलाया जाए


मैंने आपका प्रश्न देखा और उत्तर भी। (उत्तर के लिए मेरी टिप्पणी देखें) .... :-) और वास्तव में यह एक शिक्षाप्रद प्रश्न और उत्तर है।
रॉय नमिर

11
यह वास्तव में ऐसी स्थिति नहीं है जिसमें टीसीएस की जरूरत है। आप बस इसे करने के Task.FromResultलिए उपयोग कर सकते हैं । बेशक, यदि आप 4.0 का उपयोग कर रहे हैं और आपके पास Task.FromResultएक TCS का उपयोग करने के लिए नहीं है, तो अपना खुद का लिखना हैFromResult
सर्व

@Servy Task.FromResultकेवल .NET 4.5 के बाद से उपलब्ध है। इससे पहले, यह इस व्यवहार को प्राप्त करने का तरीका था।
आदि लेस्टर

@AdiLester आप उत्तर दे रहे हैं संदर्भित है Task.Run, यह 4.5+ दर्शाता है। और मेरी पिछली टिप्पणी ने विशेष रूप से .NET 4.0 को संबोधित किया।
सर्व

@Servy इस उत्तर को पढ़ने वाला हर कोई .NET 4.5+ को लक्षित नहीं कर रहा है। मेरा मानना ​​है कि यह एक अच्छा और मान्य उत्तर है जो लोगों को ओपी के सवाल पूछने में मदद करता है (जो वैसे भी .NET-4.0 को टैग किया जाता है)। किसी भी तरह से, इसे डाउनवोट करना मुझे थोड़ा बहुत लगता है, लेकिन अगर आप वास्तव में मानते हैं कि यह डाउनवोट का हकदार है तो आगे बढ़ें।
आदि लेस्टर

25

में इस ब्लॉग पोस्ट , लेवी Botelho बताता है कि कैसे उपयोग करने के लिए TaskCompletionSourceएक प्रक्रिया आप इसे लांच और उसके समाप्ति का इंतजार कर सकते हैं ऐसी है कि के लिए एक अतुल्यकालिक आवरण लिखने के लिए।

public static Task RunProcessAsync(string processPath)
{
    var tcs = new TaskCompletionSource<object>();
    var process = new Process
    {
        EnableRaisingEvents = true,
        StartInfo = new ProcessStartInfo(processPath)
        {
            RedirectStandardError = true,
            UseShellExecute = false
        }
    };
    process.Exited += (sender, args) =>
    {
        if (process.ExitCode != 0)
        {
            var errorMessage = process.StandardError.ReadToEnd();
            tcs.SetException(new InvalidOperationException("The process did not exit correctly. " +
                "The corresponding error message was: " + errorMessage));
        }
        else
        {
            tcs.SetResult(null);
        }
        process.Dispose();
    };
    process.Start();
    return tcs.Task;
}

और इसका उपयोग

await RunProcessAsync("myexecutable.exe");

14

ऐसा लगता है कि कोई भी उल्लेख नहीं किया गया है, लेकिन मुझे लगता है कि इकाई परीक्षण भी वास्तविक जीवन को पर्याप्त माना जा सकता है।

मुझे TaskCompletionSourceएक async विधि के साथ निर्भरता का मजाक उड़ाते समय उपयोगी लगता है।

परीक्षण के तहत वास्तविक कार्यक्रम में:

public interface IEntityFacade
{
  Task<Entity> GetByIdAsync(string id);
}

इकाई परीक्षणों में:

// set up mock dependency (here with NSubstitute)

TaskCompletionSource<Entity> queryTaskDriver = new TaskCompletionSource<Entity>();

IEntityFacade entityFacade = Substitute.For<IEntityFacade>();

entityFacade.GetByIdAsync(Arg.Any<string>()).Returns(queryTaskDriver.Task);

// later on, in the "Act" phase

private void When_Task_Completes_Successfully()
{
  queryTaskDriver.SetResult(someExpectedEntity);
  // ...
}

private void When_Task_Gives_Error()
{
  queryTaskDriver.SetException(someExpectedException);
  // ...
}

आखिरकार, TaskCompletionSource का यह उपयोग "एक टास्क ऑब्जेक्ट जो कोड निष्पादित नहीं करता है" का एक और मामला लगता है।


11

TaskCompletionSource का उपयोग उन टास्क ऑब्जेक्ट को बनाने के लिए किया जाता है जो कोड निष्पादित नहीं करते हैं। वास्तविक दुनिया के परिदृश्यों में, टास्कप्लेक्शन स्रोत I / O बाध्य परिचालनों के लिए आदर्श है। इस तरह, आपको ऑपरेशन की अवधि के लिए एक धागा को अवरुद्ध किए बिना कार्यों के सभी लाभ (जैसे रिटर्न मान, निरंतरता, आदि) मिलते हैं। यदि आपका "फ़ंक्शन" एक I / O बाध्य ऑपरेशन है, तो नए टास्क का उपयोग करके थ्रेड को ब्लॉक करने की अनुशंसा नहीं की जाती है । इसके बजाय, TaskCompletionSource का उपयोग करके , आप केवल यह इंगित करने के लिए एक गुलाम कार्य बना सकते हैं कि आपका I / O बाध्य ऑपरेशन खत्म हो गया है या दोष।


5

इस पोस्ट में ".NET के साथ समानांतर प्रोग्रामिंग" ब्लॉग से एक अच्छी व्याख्या के साथ एक वास्तविक दुनिया उदाहरण है । आपको वास्तव में इसे पढ़ना चाहिए, लेकिन यहाँ एक सारांश भी है।

ब्लॉग पोस्ट के लिए दो कार्यान्वयन दिखाता है:

"" विलंबित "कार्यों को बनाने के लिए एक कारखाना विधि, जो वास्तव में कुछ उपयोगकर्ता द्वारा आपूर्ति किए गए समय समाप्त होने तक निर्धारित नहीं होगी।"

दिखाए गए पहले कार्यान्वयन पर आधारित है Task<>और इसमें दो प्रमुख दोष हैं। दूसरी कार्यान्वयन पोस्ट का उपयोग करके इन्हें कम किया जाता है TaskCompletionSource<>

यह है कि दूसरा कार्यान्वयन:

public static Task StartNewDelayed(int millisecondsDelay, Action action)
{
    // Validate arguments
    if (millisecondsDelay < 0)
        throw new ArgumentOutOfRangeException("millisecondsDelay");
    if (action == null) throw new ArgumentNullException("action");

    // Create a trigger used to start the task
    var tcs = new TaskCompletionSource<object>();

    // Start a timer that will trigger it
    var timer = new Timer(
        _ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite);

    // Create and return a task that will be scheduled when the trigger fires.
    return tcs.Task.ContinueWith(_ =>
    {
        timer.Dispose();
        action();
    });
}

बेहतर tcs.Task पर इंतजार है उपयोग करने के लिए और उसके बाद के बाद) क्रिया का उपयोग (होगा
Royi Namir

5
beucase आप उस संदर्भ में वापस आ गए हैं जहाँ आपने छोड़ा था, जहाँ Continuewith संदर्भ को प्रस्तुत नहीं करता है। (डिफ़ॉल्ट रूप से नहीं) यह भी अगर अगले बयान में कार्रवाई () एक अपवाद का कारण बनता है, तो इसे पकड़ना मुश्किल होगा जहां प्रतीक्षा का उपयोग करना आपको एक नियमित अपवाद के रूप में दिखाएगा।
रॉय नमिर

3
क्यों नहीं await Task.Delay(millisecondsDelay); action(); return;(या। नेट 4.0)return Task.Delay(millisecondsDelay).ContinueWith( _ => action() );
sgnsajgon

@sgnsajgon जिसे निश्चित रूप से पढ़ना और बनाए रखना आसान होगा
JwJosefy

@JwJosefy वास्तव में, Task.Delay विधि का उपयोग कर कार्यान्वित किया जा सकता TaskCompletionSource , ठीक उसी प्रकार से ऊपर कोड के लिए। वास्तविक कार्यान्वयन यहाँ है: कार्य.स्कैन
sgnsajgon

4

यह चीजों की देखरेख हो सकती है लेकिन टास्क कम्‍पलीशन स्रोत किसी घटना के होने की प्रतीक्षा कर सकता है। चूंकि tcs.SetResult केवल एक बार सेट होता है जब घटना होती है, कॉल करने वाला कार्य का इंतजार कर सकता है।

अधिक जानकारी के लिए यह वीडियो देखें:

http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03-TipsForAsyncThreadsAndDatabinding


1
कृपया प्रासंगिक कोड या दस्तावेज़ यहां रखें क्योंकि लिंक समय के साथ बदल सकते हैं और इस उत्तर को अप्रासंगिक बना सकते हैं।
rfornal

3

मैं वास्तविक दुनिया का परिदृश्य जहां मैंने उपयोग किया TaskCompletionSourceहै, एक डाउनलोड कतार को लागू करते समय है। मेरे मामले में अगर उपयोगकर्ता 100 डाउनलोड शुरू करता है तो मैं उन सभी को एक बार में बंद नहीं करना चाहता हूं और इसलिए एक स्ट्रिंग किए गए कार्य को वापस करने के बजाय मैं संलग्न एक कार्य को वापस करता हूं TaskCompletionSource। एक बार डाउनलोड पूरा हो जाने के बाद जो कतार काम कर रही है वह कार्य पूरा हो जाता है।

यहां मुख्य अवधारणा यह है कि जब ग्राहक वास्तव में शुरू हो जाता है, तो उससे काम शुरू करने के लिए कहता है, तो मैं डिकोड कर रहा हूं। इस मामले में क्योंकि मैं नहीं चाहता कि ग्राहक को संसाधन प्रबंधन से निपटना पड़े।

ध्यान दें कि जब तक आप C # 5 कंपाइलर (VS 2012+) का उपयोग कर रहे हों, तब तक आप async / wait in .net 4 का उपयोग कर सकते हैं और अधिक विवरण के लिए यहां देखें।


0

TaskCompletionSourceजब तक इसे रद्द नहीं किया जाता, तब तक मैंने एक टास्क चलाया। इस मामले में यह एक सर्विसबस सब्सक्राइबर है जिसे मैं सामान्य रूप से तब तक चलाना चाहता हूं जब तक एप्लिकेशन चलता है।

public async Task RunUntilCancellation(
    CancellationToken cancellationToken,
    Func<Task> onCancel)
{
    var doneReceiving = new TaskCompletionSource<bool>();

    cancellationToken.Register(
        async () =>
        {
            await onCancel();
            doneReceiving.SetResult(true); // Signal to quit message listener
        });

    await doneReceiving.Task.ConfigureAwait(false); // Listen until quit signal is received.
}

1
'टास्क कॉमप्लेक्शन सोर्स' के साथ 'एसिंक्स' का उपयोग करने की आवश्यकता नहीं है क्योंकि यह पहले ही एक कार्य बना चुका है
मंदीप जंजुआ

-1

TaskCompletionSourceWaitHandleथ्रेड्स के रूप में कार्य करना है। और इसलिए हम TaskCompletionSource सटीक सिग्नलिंग करने के लिए उपयोग कर सकते हैं ।

एक उदाहरण इस प्रश्न के लिए मेरा उत्तर है: ओके क्लिक के बाद कंटेंटडायॉग्ल विलंब

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