2010-12-02 14 views
11

टास्क समांतर लाइब्रेरी का उपयोग करने से पहले, मैंने अक्सर एकाधिक थ्रेड के साथ ट्रेसिंग/त्रुटि रिपोर्टिंग का ट्रैक रखने के लिए सहसंबंधण प्रबंधक का उपयोग किया है। सक्रियता आईडी।कार्य समानांतर लाइब्रेरी में कार्य गतिविधि गतिविधि को कैसे प्रभावित करते हैं?

गतिविधि आईडी थ्रेड स्थानीय संग्रहण में संग्रहीत है, इसलिए प्रत्येक थ्रेड की अपनी प्रति प्राप्त होती है। विचार यह है कि जब आप एक थ्रेड (गतिविधि) को आग लगाते हैं, तो आप एक नई गतिविधि आईडी निर्दिष्ट करते हैं। ActivityId किसी भी अन्य ट्रेस जानकारी के साथ लॉग पर लिखा जाएगा, जिससे एकल 'गतिविधि' के लिए ट्रेस जानकारी को एकल करना संभव हो जाता है। यह डब्ल्यूसीएफ के साथ वास्तव में उपयोगी है क्योंकि गतिविधि आईडी को सेवा घटक में ले जाया जा सकता है।

यहाँ मैं किस बारे में बात कर रहा हूँ का एक उदाहरण है:

static void Main(string[] args) 
{ 
    ThreadPool.QueueUserWorkItem(new WaitCallback((o) => 
    { 
     DoWork(); 
    })); 
} 

static void DoWork() 
{ 
    try 
    { 
     Trace.CorrelationManager.ActivityId = Guid.NewGuid(); 
     //The functions below contain tracing which logs the ActivityID. 
     CallFunction1(); 
     CallFunction2(); 
     CallFunction3(); 
    } 
    catch (Exception ex) 
    { 
     Trace.Write(Trace.CorrelationManager.ActivityId + " " + ex.ToString()); 
    } 
} 

अब, TPL साथ, मैं समझता हूँ कि कई कार्य शेयर धागे है। क्या इसका मतलब यह है कि गतिविधि आईडी को मध्य-कार्य (दूसरे कार्य द्वारा) को पुन: प्रारंभ करने के लिए प्रवण होता है? क्या गतिविधि ट्रेसिंग से निपटने के लिए कोई नया तंत्र है?

+0

मेरे पास प्रस्ताव देने के लिए कुछ भी नहीं है, लेकिन मुझे इस मुद्दे में भी रूचि है। ऐसा लगता है कि वही प्रश्न सामान्य रूप से CallContext.LogicalSetData का उपयोग करके सूचना सेट पर भी लागू होता है क्योंकि यह तकनीक है जो Trace.CorrelationManager गतिविधि आईडी और लॉजिकलऑपरेशनस्टैक को संग्रहीत करने के लिए उपयोग करती है। – wageoghe

+0

@wageohe - मैं अंत में आज परीक्षण करने के लिए चारों ओर मिल गया, मेरे परिणाम पोस्ट कर दिया है :) –

+0

मैंने अपने उत्तर में कुछ और विवरण पोस्ट किए हैं। मैंने एसओ पर एक और उत्तर के लिए एक लिंक भी पोस्ट किया, एक नया सवाल जिसे मैंने यहां एसओ पर पूछा था, साथ ही साथ एक प्रश्न जो मैंने पूछा (लेकिन अभी तक 1/21/2011 के रूप में उत्तर नहीं दिया गया है) माइक्रोसॉफ्ट के समांतर एक्सटेंशन फोरम पर । शायद आपको जानकारी उपयोगी लगेगी, शायद नहीं। – wageoghe

उत्तर

6

मैंने कुछ प्रयोग चलाए और यह पता चला कि मेरे प्रश्न में धारणा गलत है - टीपीएल के साथ बनाए गए कई कार्य एक ही समय में एक ही थ्रेड पर नहीं चलते हैं।

थ्रेडलोकल स्टोरेज .NET 4.0 में टीपीएल के साथ उपयोग करने के लिए सुरक्षित है, क्योंकि थ्रेड केवल एक ही समय में एक कार्य द्वारा उपयोग किया जा सकता है।

धारणा है कि कार्यों धागे समवर्ती एक साक्षात्कार मैं DotNetRocks (क्षमा करें, मैं याद नहीं कर सकते जो दर्शाते हैं कि यह किया गया था) पर के बारे में सी # 5.0 सुना पर आधारित था साझा कर सकते हैं - तो मेरे सवाल हो सकता है (या नहीं हो सकता) जल्द ही प्रासंगिक हो जाओ।

मेरा प्रयोग कई कार्यों को शुरू करता है, और रिकॉर्ड करता है कि कितने कार्य चलते हैं, कितने समय तक उन्होंने लिया, और कितने धागे खपत किए गए। अगर कोई इसे दोहराना चाहेगा तो कोड नीचे है।

class Program 
{ 
    static void Main(string[] args) 
    { 
     int totalThreads = 100; 
     TaskCreationOptions taskCreationOpt = TaskCreationOptions.None; 
     Task task = null; 
     Stopwatch stopwatch = new Stopwatch(); 
     stopwatch.Start(); 
     Task[] allTasks = new Task[totalThreads]; 
     for (int i = 0; i < totalThreads; i++) 
     { 
      task = Task.Factory.StartNew(() => 
      { 
       DoLongRunningWork(); 
      }, taskCreationOpt); 

      allTasks[i] = task; 
     } 

     Task.WaitAll(allTasks); 
     stopwatch.Stop(); 

     Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds)); 
     Console.WriteLine(String.Format("Used {0} threads", threadIds.Count)); 
     Console.ReadKey(); 
    } 


    private static List<int> threadIds = new List<int>(); 
    private static object locker = new object(); 
    private static void DoLongRunningWork() 
    { 
     lock (locker) 
     { 
      //Keep a record of the managed thread used. 
      if (!threadIds.Contains(Thread.CurrentThread.ManagedThreadId)) 
       threadIds.Add(Thread.CurrentThread.ManagedThreadId); 
     } 
     Guid g1 = Guid.NewGuid(); 
     Trace.CorrelationManager.ActivityId = g1; 
     Thread.Sleep(3000); 
     Guid g2 = Trace.CorrelationManager.ActivityId; 
     Debug.Assert(g1.Equals(g2)); 
    } 
} 

निर्गम (निश्चित रूप से इस मशीन पर निर्भर करेगा) था:

Completed 100 tasks in 3458 milliseconds 
Used 100 threads 
+0

दिलचस्प परिणाम। मुझे आपके कोड के आधार पर एक परीक्षण कार्यक्रम का उपयोग कर Trace.CorrelationManager.ActivityId के बारे में कुछ दिलचस्प मिला है। अपने कोड का उपयोग (अधिक या कम) के रूप में करते हुए, और Trace.CorrelationManager.StartLogicalOperation/StopLogicalOperation का उपयोग करके, मुझे "अच्छे" परिणाम मिल सकते हैं। यही है, स्टार्टलोगिकलऑपरेशन/स्टॉप लॉजिकलऑपरेशन के साथ प्रत्येक कार्य (प्रतिनिधि के अंदर) को प्रदर्शित करने और ब्रैकेट करने के तरीके से कार्य का उपयोग करके, लॉजिकलऑपरेशनस्टैक हमेशा सिंक में प्रतीत होता है। हालांकि, समानांतर का उपयोग करना। इसके लिए खराब परिणाम मिल सकते हैं। मैं अपना परीक्षण उत्तर के रूप में पोस्ट करूंगा क्योंकि उसके पास कोड – wageoghe

+0

ग्रेट उत्तर है; पोस्ट करने का शुक्रिया। एक pedantic quibble हालांकि: 'सूची threadIds' फ़ील्ड शायद एक असुरक्षित जेनेरिक सूची के बजाय एक ConcurrentBag या ConcurrentDictionary होना चाहिए (शब्दकोश, उदाहरण के लिए, यदि आप taskId को threadId से जोड़ना चाहते हैं, या बस इसे एक समवर्ती हैशटेबल के रूप में उपयोग करें और .value अनदेखा करें)। –

3

मेरी इस पोस्टिंग को माफ करें:

Completed 100 tasks in 23097 milliseconds 
Used 23 threads 

TaskCreationOptions.LongRunning को taskCreationOpt बदलने अलग परिणाम दे दी है एक उत्तर के रूप में यह वास्तव में आपके प्रश्न का उत्तर नहीं देता है, हालांकि, यह आपके प्रश्न से संबंधित है क्योंकि यह सहसंबंध प्रबंधक व्यवहार और धागे/कार्य/आदि से संबंधित है। मैं multithreading परिदृश्यों में अतिरिक्त संदर्भ प्रदान करने के लिए CorrelationManager के LogicalOperationStack (और StartLogicalOperation/StopLogicalOperation विधियों) का उपयोग कर देख रहा हूं।

मैंने आपका उदाहरण लिया और समानांतर में समानांतर में काम करने की क्षमता जोड़ने के लिए थोड़ा सा संशोधित किया। इसके लिए। इसके अलावा, मैं StartLogicalOperation/StopLogicalOperation का उपयोग ब्रैकेट (आंतरिक रूप से) DoLongRunningWork पर करता हूं।

DoLongRunningWork 
    StartLogicalOperation 
    Thread.Sleep(3000) 
    StopLogicalOperation 

मैं ने पाया है कि अगर मैं अपने कोड में ये तार्किक संचालन जोड़ने (कम या ज्यादा होता है), तार्किक operatins के सभी सिंक में रहते हैं (: वैचारिक रूप से, DoLongRunningWork इस हर बार निष्पादित किया जाता है की तरह कुछ करता है ढेर पर संचालन की हमेशा अपेक्षित संख्या और ढेर पर संचालन के मूल्य हमेशा अपेक्षित होते हैं)।

मेरे अपने कुछ परीक्षणों में मैंने पाया कि यह हमेशा मामला नहीं था। तार्किक ऑपरेशन स्टैक "दूषित" हो रहा था। सबसे अच्छा स्पष्टीकरण मैं साथ आ सकता हूं कि "बच्चे" धागे से निकलने पर कॉलकॉन्टेक्स्ट जानकारी की "विलय" वापस "मूल" थ्रेड संदर्भ में "पुरानी" बाल थ्रेड संदर्भ जानकारी (लॉजिकल ऑपरेशन) " विरासत "एक और भाई बच्चे धागे द्वारा।

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

यह मुझे एक लंबे समय यह पता लगाने की क्यों LogicalOperationStack का व्यवहार आपके उदाहरण के अपने संशोधित संस्करण की तुलना में मेरे उदाहरण में अलग था ले लिया। आखिरकार मैंने देखा कि मेरे कोड में मैंने पूरे कार्यक्रम को लॉजिकल ऑपरेशन में ब्रैकेट किया था, जबकि आपके टेस्ट प्रोग्राम के मेरे संशोधित संस्करण में मैंने नहीं किया था। निहितार्थ यह है कि मेरे परीक्षण कार्यक्रम में, हर बार जब मेरा "काम" किया गया था (DoLongRunningWork के समान), पहले से ही एक तार्किक ऑपरेशन प्रभावी था। आपके परीक्षण कार्यक्रम के मेरे संशोधित संस्करण में, मैंने पूरे कार्यक्रम को लॉजिकल ऑपरेशन में ब्रैकेट नहीं किया था।

तो, जब मैं अपने परीक्षण कार्यक्रम एक तार्किक आपरेशन में और अगर मैं Parallel.For उपयोग कर रहा हूँ पूरे कार्यक्रम ब्रैकेट करने के लिए संशोधित, मैं बिल्कुल वैसा ही समस्या हुई थी।

ऊपर वैचारिक मॉडल का उपयोग करना, यह सफलतापूर्वक चलेंगे:

Parallel.For 
    DoLongRunningWork 
    StartLogicalOperation 
    Sleep(3000) 
    StopLogicalOperation 

यह अंततः जाहिरा तौर पर सिंक LogicalOperationStack के बाहर एक के कारण जोर करते हैं:

StartLogicalOperation 
Parallel.For 
    DoLongRunningWork 
    StartLogicalOperation 
    Sleep(3000) 
    StopLogicalOperation 
StopLogicalOperation 

यहाँ मेरी नमूना कार्यक्रम है। यह आपके जैसा ही है कि इसमें एक DoLongRunningWork विधि है जो ActivityId के साथ-साथ LogicalOperationStack में हेरफेर करती है। मेरे पास DoLongRunningWork की लात मारने के दो स्वाद भी हैं। एक स्वाद समान कार्यों का उपयोग करता है जो एक समानांतर का उपयोग करता है। के लिए। प्रत्येक स्वाद को भी निष्पादित किया जा सकता है कि संपूर्ण समांतर ऑपरेशन लॉजिकल ऑपरेशन में संलग्न है या नहीं। तो, समानांतर संचालन निष्पादित करने के कुल 4 तरीके हैं। प्रत्येक को आज़माने के लिए, वांछित "उपयोग करें ..." विधि को असम्बद्ध करें, पुनः संकलित करें और चलाएं। UseTasks, UseTasks(true), और UseParallelFor सभी को पूरा होने के लिए तैयार होना चाहिए। UseParallelFor(true) कुछ बिंदु पर जोर देगी क्योंकि LogicalOperationStack में प्रविष्टियों की अपेक्षित संख्या नहीं है।

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 
using System.Threading; 
using System.Threading.Tasks; 

namespace CorrelationManagerParallelTest 
{ 
    class Program 
    {  
    static void Main(string[] args)  
    { 
     //UseParallelFor(true) will assert because LogicalOperationStack will not have expected 
     //number of entries, all others will run to completion. 

     UseTasks(); //Equivalent to original test program with only the parallelized 
         //operation bracketed in logical operation. 
     ////UseTasks(true); //Bracket entire UseTasks method in logical operation 
     ////UseParallelFor(); //Equivalent to original test program, but use Parallel.For 
          //rather than Tasks. Bracket only the parallelized 
          //operation in logical operation. 
     ////UseParallelFor(true); //Bracket entire UseParallelFor method in logical operation 
    }  

    private static List<int> threadIds = new List<int>();  
    private static object locker = new object();  

    private static int mainThreadId = Thread.CurrentThread.ManagedThreadId; 

    private static int mainThreadUsedInDelegate = 0; 

    // baseCount is the expected number of entries in the LogicalOperationStack 
    // at the time that DoLongRunningWork starts. If the entire operation is bracketed 
    // externally by Start/StopLogicalOperation, then baseCount will be 1. Otherwise, 
    // it will be 0. 
    private static void DoLongRunningWork(int baseCount)  
    { 
     lock (locker) 
     { 
     //Keep a record of the managed thread used.    
     if (!threadIds.Contains(Thread.CurrentThread.ManagedThreadId)) 
      threadIds.Add(Thread.CurrentThread.ManagedThreadId); 

     if (Thread.CurrentThread.ManagedThreadId == mainThreadId) 
     { 
      mainThreadUsedInDelegate++; 
     } 
     }   

     Guid lo1 = Guid.NewGuid(); 
     Trace.CorrelationManager.StartLogicalOperation(lo1); 

     Guid g1 = Guid.NewGuid();   
     Trace.CorrelationManager.ActivityId = g1; 

     Thread.Sleep(3000);   

     Guid g2 = Trace.CorrelationManager.ActivityId; 
     Debug.Assert(g1.Equals(g2)); 

     //This assert, LogicalOperation.Count, will eventually fail if there is a logical operation 
     //in effect when the Parallel.For operation was started. 
     Debug.Assert(Trace.CorrelationManager.LogicalOperationStack.Count == baseCount + 1, string.Format("MainThread = {0}, Thread = {1}, Count = {2}, ExpectedCount = {3}", mainThreadId, Thread.CurrentThread.ManagedThreadId, Trace.CorrelationManager.LogicalOperationStack.Count, baseCount + 1)); 
     Debug.Assert(Trace.CorrelationManager.LogicalOperationStack.Peek().Equals(lo1), string.Format("MainThread = {0}, Thread = {1}, Count = {2}, ExpectedCount = {3}", mainThreadId, Thread.CurrentThread.ManagedThreadId, Trace.CorrelationManager.LogicalOperationStack.Peek(), lo1)); 

     Trace.CorrelationManager.StopLogicalOperation(); 
    } 

    private static void UseTasks(bool encloseInLogicalOperation = false) 
    { 
     int totalThreads = 100; 
     TaskCreationOptions taskCreationOpt = TaskCreationOptions.None; 
     Task task = null; 
     Stopwatch stopwatch = new Stopwatch(); 
     stopwatch.Start(); 

     if (encloseInLogicalOperation) 
     { 
     Trace.CorrelationManager.StartLogicalOperation(); 
     } 

     Task[] allTasks = new Task[totalThreads]; 
     for (int i = 0; i < totalThreads; i++) 
     { 
     task = Task.Factory.StartNew(() => 
     { 
      DoLongRunningWork(encloseInLogicalOperation ? 1 : 0); 
     }, taskCreationOpt); 
     allTasks[i] = task; 
     } 
     Task.WaitAll(allTasks); 

     if (encloseInLogicalOperation) 
     { 
     Trace.CorrelationManager.StopLogicalOperation(); 
     } 

     stopwatch.Stop(); 
     Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds)); 
     Console.WriteLine(String.Format("Used {0} threads", threadIds.Count)); 
     Console.WriteLine(String.Format("Main thread used in delegate {0} times", mainThreadUsedInDelegate)); 

     Console.ReadKey(); 
    } 

    private static void UseParallelFor(bool encloseInLogicalOperation = false) 
    { 
     int totalThreads = 100; 
     Stopwatch stopwatch = new Stopwatch(); 
     stopwatch.Start(); 

     if (encloseInLogicalOperation) 
     { 
     Trace.CorrelationManager.StartLogicalOperation(); 
     } 

     Parallel.For(0, totalThreads, i => 
     { 
     DoLongRunningWork(encloseInLogicalOperation ? 1 : 0); 
     }); 

     if (encloseInLogicalOperation) 
     { 
     Trace.CorrelationManager.StopLogicalOperation(); 
     } 

     stopwatch.Stop(); 
     Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds)); 
     Console.WriteLine(String.Format("Used {0} threads", threadIds.Count)); 
     Console.WriteLine(String.Format("Main thread used in delegate {0} times", mainThreadUsedInDelegate)); 

     Console.ReadKey(); 
    } 

    } 
} 

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

[संपादित करें]

विभिन्न धागा/ThreadPool/कार्य/समानांतर contstructs से कुछ के साथ LogicalOperationStack और/या CallContext.LogicalSetData उपयोग के बारे में अधिक जानकारी के लिए this question को मेरा उत्तर देखें।

भी मेरे सवाल का यहाँ देखें अतः LogicalOperationStack और समानांतर एक्सटेंशन के बारे में पर: http://social.msdn.microsoft.com/Forums/en-US/parallelextensions/thread/7c5c3051-133b-4814-9db0-fc0039b4f9d9

मेरी परीक्षण यह ट्रेस की तरह लग रहा है: Is CorrelationManager.LogicalOperationStack compatible with Parallel.For, Tasks, Threads, etc

अंत में, यह भी मेरे सवाल का माइक्रोसॉफ्ट के समानांतर एक्सटेंशन फ़ोरम पर यहाँ देखें। CorrelationManager.LogicalOperationStack समानांतर का उपयोग करते समय दूषित हो सकता है। या समानांतर। इन्वोक यदि आप मुख्य थ्रेड में लॉजिकल ऑपरेशन प्रारंभ करते हैं और फिर प्रतिनिधि में लॉजिकल ऑपरेशंस प्रारंभ/बंद करते हैं। मेरे परीक्षणों में (ऊपर दिए गए दो लिंक में से किसी एक को देखें) LogicalOperationStack में हमेशा 2 प्रविष्टियां होनी चाहिए जब DoLongRunningWork निष्पादित हो रहा है (यदि मैं विभिन्न तकनीकों का उपयोग करके DoLongRunningWork को लात मारने से पहले मुख्य धागे में लॉजिकल ऑपरेशन शुरू करता हूं)। तो, "दूषित" से मेरा मतलब है कि लॉजिकलऑपरेशनस्टैक में अंततः 2 से अधिक प्रविष्टियां होंगी।

जो मैं कह सकता हूं उससे, शायद यह है क्योंकि समांतर। फॉर और समानांतर। इनवोक मुख्य थ्रेड का उपयोग "कार्यकर्ता" धागे में से एक के रूप में DoLongRunningWork कार्रवाई करने के लिए करता है।

LogicalOperationStack के व्यवहार की नकल करने के लिए CallContext.LogicalSetData में संग्रहीत एक स्टैक का उपयोग करना (Log4net के लॉजिकल थ्रेड कॉन्टेक्स्ट.स्टैक के समान जो कॉलकॉन्टेक्स्ट.SetData के माध्यम से संग्रहीत है) भी इससे भी बदतर परिणाम उत्पन्न करता है। यदि मैं संदर्भ बनाए रखने के लिए इस तरह के ढेर का उपयोग कर रहा हूं, तो यह लगभग सभी परिदृश्यों में दूषित हो जाता है (यानी प्रविष्टियों की अपेक्षित संख्या नहीं है) जहां मेरे पास मुख्य धागे में "लॉजिकल ऑपरेशन" है और प्रत्येक पुनरावृत्ति में लॉजिकल ऑपरेशन है/DoLongRunningWork प्रतिनिधि के निष्पादन।

+2

डाउनवोट? कोई टिप्पणी नहीं? धन्यवाद। – wageoghe

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