2011-01-19 16 views
17

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

कार्य कुछ इस तरह ऊपर पंक्तिबद्ध कर रहे हैं:

private Task workQueue; 
private void DoWorkAsync 
    (Action<WorkCompletedEventArgs> callback, CancellationToken token) 
{ 
    if (workQueue == null) 
    { 
     workQueue = Task.Factory.StartWork 
      (() => DoWork(callback, token), token); 
    } 
    else 
    { 
     workQueue.ContinueWork(t => DoWork(callback, token), token); 
    } 
} 

DoWork विधि एक लंबी चलने वाली कॉल होता है, इसलिए यह उतना ही आसान नहीं है लगातार token.IsCancellationRequested की स्थिति जांचने और प्रति सहिष्णु के रूप में यदि/जब एक रद्द का पता चला है । लंबे समय तक चलने वाला कार्य टास्क निरंतरता को तब तक अवरुद्ध कर देगा जब तक यह कार्य समाप्त न हो जाए, भले ही कार्य रद्द हो जाए।

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

ध्यान देने योग्य महत्वपूर्ण बात यह है कि मूल कार्य से पहले निरंतरता आग पूर्ण करती है।

प्रयास # 1: एक आंतरिक कार्य

static void Main(string[] args) 
{ 
    CancellationTokenSource cts = new CancellationTokenSource(); 
    var token = cts.Token; 
    token.Register(() => Console.WriteLine("Token cancelled")); 
    // Initial work 
    var t = Task.Factory.StartNew(() => 
    { 
     Console.WriteLine("Doing work"); 

     // Wrap the long running work in a task, and then wait for it to complete 
     // or the token to be cancelled. 
     var innerT = Task.Factory.StartNew(() => Thread.Sleep(3000), token); 
     innerT.Wait(token); 
     token.ThrowIfCancellationRequested(); 
     Console.WriteLine("Completed."); 
    } 
    , token); 
    // Second chunk of work which, in the real world, would be identical to the 
    // first chunk of work. 
    t.ContinueWith((lastTask) => 
     { 
      Console.WriteLine("Continuation started"); 
     }); 

    // Give the user 3s to cancel the first batch of work 
    Console.ReadKey(); 
    if (t.Status == TaskStatus.Running) 
    { 
     Console.WriteLine("Cancel requested"); 
     cts.Cancel(); 
     Console.ReadKey(); 
    } 
} 

यह काम करता है, लेकिन "innerT" टास्क मेरे लिए अत्यंत kludgey महसूस करता है। मुझे अपने कोड के सभी हिस्सों को दोबारा करने के लिए मजबूर करने की भी कमी है जो इस तरह से काम को कतार में रखता है, एक नए कार्य में सभी लंबी चलने वाली कॉलों को लपेटने की आवश्यकता के द्वारा।

प्रयास # 2: TaskCompletionSource

static void Main(string[] args) 
{ var tcs = new TaskCompletionSource<object>(); 
//Wire up the token's cancellation to trigger the TaskCompletionSource's cancellation 
    CancellationTokenSource cts = new CancellationTokenSource(); 
    var token = cts.Token; 
    token.Register(() => 
     { Console.WriteLine("Token cancelled"); 
      tcs.SetCanceled(); 
      }); 
    var innerT = Task.Factory.StartNew(() => 
     { 
      Console.WriteLine("Doing work"); 
      Thread.Sleep(3000); 
      Console.WriteLine("Completed."); 
    // When the work has complete, set the TaskCompletionSource so that the 
    // continuation will fire. 
      tcs.SetResult(null); 
     }); 
    // Second chunk of work which, in the real world, would be identical to the 
    // first chunk of work. 
    // Note that we continue when the TaskCompletionSource's task finishes, 
    // not the above innerT task. 
    tcs.Task.ContinueWith((lastTask) => 
     { 
     Console.WriteLine("Continuation started"); 
     }); 
    // Give the user 3s to cancel the first batch of work 
    Console.ReadKey(); 
    if (innerT.Status == TaskStatus.Running) 
    { 
     Console.WriteLine("Cancel requested"); 
     cts.Cancel(); 
     Console.ReadKey(); 
    } 
} 

संवारता फिर इस काम करता है, लेकिन अब मैं दो समस्याएं हैं:

एक) यह लगता है जैसे मैं कभी नहीं का उपयोग कर इसे परिणाम है द्वारा TaskCompletionSource कोस रहा हूँ, और जब मैंने अपना काम पूरा कर लिया है तो बस शून्य स्थापित करें।

बी) निरंतरता को व्यवस्थित करने के लिए मुझे काम के अद्वितीय कार्यसमूह स्रोत की पिछली इकाई पर एक हैंडल रखने की आवश्यकता है, और इसके लिए बनाए गए कार्य को नहीं। यह तकनीकी रूप से संभव है, लेकिन फिर घबराहट और अजीब लगता है।

यहां से कहाँ जाना है?

दोहराने के लिए, मेरा प्रश्न यह है कि: इन समस्याओं में से किसी एक को इस समस्या से निपटने के लिए "सही" तरीका है, या क्या कोई और सही/सुरुचिपूर्ण समाधान है जो मुझे समय से पहले लंबे समय तक चलने वाले कार्य को निरस्त करने और तुरंत शुरू करने की अनुमति देगा एक निरंतरता? मेरी वरीयता कम प्रभाव वाले समाधान के लिए है, लेकिन अगर यह सही काम है तो मैं कुछ बड़े रिफैक्टरिंग करने के इच्छुक हूं।

वैकल्पिक रूप से, टीपीएल नौकरी के लिए भी सही उपकरण है, या क्या मुझे एक बेहतर कार्य क्यूइंग तंत्र गुम है। मेरा लक्ष्य ढांचा .NET 4.0 है।

+0

मैं भी प्रश्न पूछा यहाँ पर: http://social.msdn.microsoft.com/Forums/en/parallelextensions/thread/d0bcb415-fb1e-42e4-90f8-c43a088537fb –

उत्तर

8

असली मुद्दा यह है कि DoWork में लंबी चल रही कॉल रद्दीकरण-जागरूक नहीं है। यदि मैं सही ढंग से समझता हूं, तो आप यहां जो भी कर रहे हैं वह वास्तव में लंबे समय तक चलने वाले काम को रद्द नहीं कर रहा है, बल्कि केवल निरंतर निष्पादन की अनुमति देता है, और जब कार्य रद्द किए गए कार्य पर पूरा होता है, परिणाम को अनदेखा कर देता है।उदाहरण के लिए, यदि आपने CrunchNumbers() को कॉल करने के लिए आंतरिक कार्य पैटर्न का उपयोग किया है, जिसमें कई मिनट लगते हैं, बाहरी कार्य को रद्द करने से निरंतरता हो सकती है, लेकिन क्रंचनबर्स() पूर्ण होने तक पृष्ठभूमि में निष्पादित करना जारी रखेगा।

मुझे नहीं लगता कि आपके लंबे समय से चलने वाले कॉल समर्थन रद्दीकरण करने के अलावा इस के आसपास कोई वास्तविक तरीका है। अक्सर यह संभव नहीं है (वे एपीआई कॉल अवरुद्ध कर सकते हैं, रद्दीकरण के लिए कोई एपीआई समर्थन नहीं है।) जब ऐसा होता है, तो यह वास्तव में एपीआई में एक दोष है; आप यह देखने के लिए जांच सकते हैं कि क्या वैकल्पिक API कॉल हैं जिनका उपयोग ऑपरेशन को रद्द करने के तरीके में करने के लिए किया जा सकता है। इस पर एक हैक दृष्टिकोण टास्क द्वारा उपयोग किए जाने वाले अंतर्निहित थ्रेड का संदर्भ कैप्चर करना है और फिर कार्य को प्रारंभ करना है और फिर थ्रेड कॉल करें। अंतराल। यह विभिन्न नींद के राज्यों से धागे को जगाएगा और इसे समाप्त करने की अनुमति देगा, लेकिन संभावित रूप से खराब तरीके से। सबसे खराब मामला, आप थ्रेड.बॉर्ट को भी कॉल कर सकते हैं, लेकिन यह और भी समस्याग्रस्त है और अनुशंसित नहीं है।


यहां एक प्रतिनिधि-आधारित रैपर पर एक स्टैब है। यह अवांछित है, लेकिन मुझे लगता है कि यह चाल करेगा; अगर आप इसे काम करते हैं और फिक्सेस/सुधार करते हैं तो जवाब को संपादित करने के लिए स्वतंत्र महसूस करें।

public sealed class AbandonableTask 
{ 
    private readonly CancellationToken _token; 
    private readonly Action _beginWork; 
    private readonly Action _blockingWork; 
    private readonly Action<Task> _afterComplete; 

    private AbandonableTask(CancellationToken token, 
          Action beginWork, 
          Action blockingWork, 
          Action<Task> afterComplete) 
    { 
     if (blockingWork == null) throw new ArgumentNullException("blockingWork"); 

     _token = token; 
     _beginWork = beginWork; 
     _blockingWork = blockingWork; 
     _afterComplete = afterComplete; 
    } 

    private void RunTask() 
    { 
     if (_beginWork != null) 
      _beginWork(); 

     var innerTask = new Task(_blockingWork, 
           _token, 
           TaskCreationOptions.LongRunning); 
     innerTask.Start(); 

     innerTask.Wait(_token); 
     if (innerTask.IsCompleted && _afterComplete != null) 
     { 
      _afterComplete(innerTask); 
     } 
    } 

    public static Task Start(CancellationToken token, 
          Action blockingWork, 
          Action beginWork = null, 
          Action<Task> afterComplete = null) 
    { 
     if (blockingWork == null) throw new ArgumentNullException("blockingWork"); 

     var worker = new AbandonableTask(token, beginWork, blockingWork, afterComplete); 
     var outerTask = new Task(worker.RunTask, token); 
     outerTask.Start(); 
     return outerTask; 
    } 
} 
+0

आपकी समझ सही है। हम अपने "क्रंचनबर्स" को पूरा होने के लिए अनुमति देने के साथ ठीक हैं, लेकिन जब भी यह आता है तो परिणाम को अनदेखा कर दिया जाएगा। –

+0

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

+0

रैपर कोड के लिए धन्यवाद - बड़े पैमाने पर विकृतियों ने मुझे कल कोशिश करने से रोक दिया - उम्मीद है कि आज सवार होगा। मैं वर्तमान में अपने प्रयास # 2 की एक रैपिंग-अप का पीछा कर रहा हूं (आपका प्रयास # 1 का लपेटना है), इसलिए एक बार जब मैं ऐसा कर लेता हूं तो मैं आपके कोड के साथ भी प्रयोग करूंगा। ध्यान दें, मुझे स्टीफन टब से यहां प्रतिक्रिया मिली है: http://social.msdn.microsoft.com/Forums/en/parallelextensions/thread/d0bcb415-fb1e-42e4-90f8-c43a088537fb –

संबंधित मुद्दे