मैं टास्क पर समकालिक निरंतरता को कैसे रोक सकता हूं?


83

मेरे पास कुछ लाइब्रेरी (सॉकेट नेटवर्किंग) कोड है जो Taskअनुरोधों के आधार पर लंबित प्रतिक्रियाओं के लिए एक- आधारित एपीआई प्रदान करता है TaskCompletionSource<T>। हालांकि, टीपीएल में एक झुंझलाहट है कि यह तुल्यकालिक निरंतरता को रोकने के लिए असंभव प्रतीत होता है। मैं ऐसा करने में सक्षम होना चाहूंगा :

  • यह बताएं TaskCompletionSource<T>कि कॉल करने वालों के साथ संलग्न करने की अनुमति नहीं देनी चाहिए TaskContinuationOptions.ExecuteSynchronously, या
  • परिणाम ( SetResult/ TrySetResult) को एक तरह से सेट करें जो निर्दिष्ट करता है कि TaskContinuationOptions.ExecuteSynchronouslyइसे अनदेखा किया जाना चाहिए, इसके बजाय पूल का उपयोग करना चाहिए

विशेष रूप से, मेरे पास मुद्दा यह है कि आने वाले डेटा को एक समर्पित पाठक द्वारा संसाधित किया जा रहा है, और यदि कोई कॉलर TaskContinuationOptions.ExecuteSynchronouslyउनके साथ संलग्न कर सकता है तो वह पाठक को रोक सकता है (जो कि उन्हें केवल इससे अधिक प्रभावित करता है)। पहले, मैंने इसके चारों ओर कुछ हैकरी द्वारा काम किया है जो पता लगाता है कि क्या कोई निरंतरता मौजूद है, और अगर वे इसे पूरा करने पर जोर देते हैं ThreadPool, हालांकि यह महत्वपूर्ण प्रभाव पड़ता है यदि कॉलर ने अपनी कार्य कतार को संतृप्त किया है, क्योंकि पूरा होने पर कार्रवाई नहीं होगी। समयानुकूल ढंग से। यदि वे Task.Wait()(या समान) उपयोग कर रहे हैं , तो वे अनिवार्य रूप से खुद को गतिरोध करेंगे। इसी तरह, यह इसलिए है कि पाठक श्रमिकों का उपयोग करने के बजाय एक समर्पित सूत्र पर है।

इसलिए; इससे पहले कि मैं कोशिश करूँ और टीपीएल टीम को नंगा करूँ: क्या मुझे एक विकल्प याद आ रहा है?

प्रमुख बिंदु:

  • मैं नहीं चाहता कि बाहरी कॉलर्स मेरे धागे को हाईजैक करने में सक्षम हों
  • मैं ThreadPoolएक कार्यान्वयन के रूप में उपयोग नहीं कर सकता , क्योंकि पूल के संतृप्त होने पर इसे काम करने की आवश्यकता होती है

उत्पादन (उत्पादन समय के आधार पर अलग-अलग हो सकता है) के उदाहरण नीचे दिए गए हैं:

Continuation on: Main thread
Press [return]
Continuation on: Thread pool

समस्या यह है कि एक यादृच्छिक कॉलर "मेन थ्रेड" पर एक निरंतरता प्राप्त करने में कामयाब रहा। वास्तविक कोड में, यह प्राथमिक पाठक को बाधित करेगा; बुरी चीजें!

कोड:

using System;
using System.Threading;
using System.Threading.Tasks;

static class Program
{
    static void Identify()
    {
        var thread = Thread.CurrentThread;
        string name = thread.IsThreadPoolThread
            ? "Thread pool" : thread.Name;
        if (string.IsNullOrEmpty(name))
            name = "#" + thread.ManagedThreadId;
        Console.WriteLine("Continuation on: " + name);
    }
    static void Main()
    {
        Thread.CurrentThread.Name = "Main thread";
        var source = new TaskCompletionSource<int>();
        var task = source.Task;
        task.ContinueWith(delegate {
            Identify();
        });
        task.ContinueWith(delegate {
            Identify();
        }, TaskContinuationOptions.ExecuteSynchronously);
        source.TrySetResult(123);
        Console.WriteLine("Press [return]");
        Console.ReadLine();
    }
}

2
मैं TaskCompletionSourceअपने एपीआई के साथ सीधे कॉल को रोकने की कोशिश करूँगा ContinueWith, क्योंकि न तो TaskCompletionSource, और न ही Taskउनसे विरासत के लिए अच्छी तरह से सूट नहीं करता।
डेनिस

1
@ स्पष्ट होने के लिए, यह वास्तव में है Taskकि उजागर है, नहीं TaskCompletionSource। यह (एक अलग एपीआई को उजागर करना) तकनीकी रूप से एक विकल्प है, लेकिन यह सिर्फ इसके लिए एक बहुत ही चरम बात है ... मुझे यकीन नहीं है कि यह इसे सही ठहराता है
मार्क ग्रेवेल

2
@MattH नहीं वास्तव में - यह सिर्फ सवाल rephrases: या तो आप का उपयोग ThreadPoolइस के लिए (जो मैं पहले ही उल्लेख किया है - यह समस्याओं का कारण बनता है), या आप एक समर्पित धागा "निरंतरता लंबित" है, और फिर वे (साथ continations ExecuteSynchronouslyनिर्दिष्ट) का अपहरण कर सकते हैं कि बजाय एक - जो वास्तव में एक ही समस्या का कारण बनता है, क्योंकि इसका मतलब है कि अन्य संदेशों के लिए निरंतरता, रुक जा सकता है जो फिर से प्रभाव डालता है एकाधिक कॉल
मार्क Gravell

3
@ और कहा कि (यह काम कर रहा है जैसे कि सभी कॉलर्स निष्पादन के बिना ContinueWith का उपयोग करते हैं) ठीक वही है जो मैं प्राप्त करना चाहता हूं। समस्या यह है कि यदि मेरा पुस्तकालय किसी टास्क को सौंपता है, तो वे बहुत अवांछनीय कुछ कर सकते हैं: वे मेरे पाठक को (अनजाने में) निष्पादन-सिंक का उपयोग करके बाधित कर सकते हैं। यह बेहद खतरनाक है, यही वजह है कि मैं इसे लाइब्रेरी के अंदर से रोकना चाहूंगा ।
मार्क Gravell

2
@Andrey: a: बहुत सारे कार्यों को पहली जगह में निरंतरता नहीं मिलती (विशेषकर बैच वर्क करते समय) - यह हर कार्य को एक करने के लिए मजबूर करेगा , और b: यहां तक ​​कि जिन लोगों के पास एक निरंतरता होती है, उनमें बहुत अधिक जटिलता होती है, ओवरहेड, और कार्यकर्ता ऑप्स। यह मायने रखता है।
मार्क Gravell

जवाबों:


50

.NET 4.6 में नया:

नेट 4.6 एक नया होता है TaskCreationOptions: RunContinuationsAsynchronously


चूंकि आप निजी क्षेत्रों तक पहुँचने के लिए प्रतिबिंब का उपयोग करने के लिए तैयार हैं ...

आप TASK_STATE_THREAD_WAS_ABORTEDध्वज के साथ TCS के कार्य को चिह्नित कर सकते हैं , जिसके कारण सभी निरंतरताएँ इनलाइन नहीं होंगी।

const int TASK_STATE_THREAD_WAS_ABORTED = 134217728;

var stateField = typeof(Task).GetField("m_stateFlags", BindingFlags.NonPublic | BindingFlags.Instance);
stateField.SetValue(task, (int) stateField.GetValue(task) | TASK_STATE_THREAD_WAS_ABORTED);

संपादित करें:

परावर्तन उत्सर्जन का उपयोग करने के बजाय, मेरा सुझाव है कि आप अभिव्यक्ति का उपयोग करें। यह बहुत अधिक पठनीय है और पीसीएल-संगत होने का लाभ है:

var taskParameter = Expression.Parameter(typeof (Task));
const string stateFlagsFieldName = "m_stateFlags";
var setter =
    Expression.Lambda<Action<Task>>(
        Expression.Assign(Expression.Field(taskParameter, stateFlagsFieldName),
            Expression.Or(Expression.Field(taskParameter, stateFlagsFieldName),
                Expression.Constant(TASK_STATE_THREAD_WAS_ABORTED))), taskParameter).Compile();

प्रतिबिंब का उपयोग किए बिना:

अगर किसी की दिलचस्पी है, तो मैंने इसे रिफ्लेक्शन के बिना करने का एक तरीका निकाला है, लेकिन यह थोड़ा "गंदा" है, और निश्चित रूप से एक गैर-नगण्य पूर्ण जुर्माना वहन करता है:

try
{
    Thread.CurrentThread.Abort();
}
catch (ThreadAbortException)
{
    source.TrySetResult(123);
    Thread.ResetAbort();
}

3
@MarcGravell टीपीएल टीम के लिए कुछ छद्म नमूना बनाने के लिए इसका उपयोग करें और निर्माण विकल्प या कुछ के माध्यम से ऐसा करने में सक्षम होने के बारे में एक परिवर्तन अनुरोध करें।
एडम हल्ड्सवर्थ

1
@Adam हाँ, अगर आप इस ध्वज की तुलना में "क्या यह कारण बनता है" के बजाय "क्या यह करता है" कॉल करने के लिए किया था, यह कुछ ऐसा होगा TaskCreationOptions.DoNotInline- और यहां तक कि करने के लिए एक ctor हस्ताक्षर परिवर्तन की जरूरत नहीं होगीTaskCompletionSource
मार्क Gravell

2
पी; @AdamHouldsworth और चिंता नहीं, मैं पहले से ही उन्हें एक ही ईमेल कर रहा हूँ है
मार्क Gravell

1
आपकी रुचि के लिए: यहाँ यह, अनुकूलित है के माध्यम से ILGeneratorआदि: github.com/StackExchange/StackExchange.Redis/blob/master/...
मार्क Gravell

1
@ नोसरियोप यूप, उन्हें चेक किया - धन्यवाद; वे सभी ठीक हैं IMO; मैं मानता हूं कि यह शुद्ध वर्कअराउंड है, लेकिन इसके सही परिणाम हैं।
मार्क Gravell

9

मुझे नहीं लगता कि टीपीएल में कुछ भी है जो निरंतरता पर स्पष्ट एपीआई नियंत्रण प्रदान करेगा TaskCompletionSource.SetResult। मैंने परिदृश्यों के लिए इस व्यवहार को नियंत्रित करने के लिए अपना प्रारंभिक उत्तर रखने का निर्णय लिया async/await

यहाँ एक और समाधान है जो अतुल्यकालिक को लागू करता है ContinueWith, अगर tcs.SetResult-triggered निरंतरता एक ही धागे SetResultपर होती है जिसे निम्नलिखित कहा जाता है:

public static class TaskExt
{
    static readonly ConcurrentDictionary<Task, Thread> s_tcsTasks =
        new ConcurrentDictionary<Task, Thread>();

    // SetResultAsync
    static public void SetResultAsync<TResult>(
        this TaskCompletionSource<TResult> @this,
        TResult result)
    {
        s_tcsTasks.TryAdd(@this.Task, Thread.CurrentThread);
        try
        {
            @this.SetResult(result);
        }
        finally
        {
            Thread thread;
            s_tcsTasks.TryRemove(@this.Task, out thread);
        }
    }

    // ContinueWithAsync, TODO: more overrides
    static public Task ContinueWithAsync<TResult>(
        this Task<TResult> @this,
        Action<Task<TResult>> action,
        TaskContinuationOptions continuationOptions = TaskContinuationOptions.None)
    {
        return @this.ContinueWith((Func<Task<TResult>, Task>)(t =>
        {
            Thread thread = null;
            s_tcsTasks.TryGetValue(t, out thread);
            if (Thread.CurrentThread == thread)
            {
                // same thread which called SetResultAsync, avoid potential deadlocks

                // using thread pool
                return Task.Run(() => action(t));

                // not using thread pool (TaskCreationOptions.LongRunning creates a normal thread)
                // return Task.Factory.StartNew(() => action(t), TaskCreationOptions.LongRunning);
            }
            else
            {
                // continue on the same thread
                var task = new Task(() => action(t));
                task.RunSynchronously();
                return Task.FromResult(task);
            }
        }), continuationOptions).Unwrap();
    }
}

टिप्पणी को संबोधित करने के लिए अपडेट किया गया:

मैं फोन करने वाले को नियंत्रित नहीं करता - मैं उन्हें एक विशिष्ट जारी रखने के लिए उपयोग नहीं कर सकता-साथ: यदि मैं कर सकता था, तो समस्या पहले से मौजूद नहीं होगी

मुझे पता नहीं था कि आप कॉलर को नियंत्रित नहीं करते हैं। फिर भी, यदि आप इसे नियंत्रित नहीं करते हैं, तो आप शायद कॉल करने वाले को सीधेTaskCompletionSource वस्तु नहीं दे रहे हैं । तार्किक रूप से, आप इसका टोकन भाग पास करेंगे, अर्थात । किस मामले में, समाधान और भी आसान हो सकता है, ऊपर एक और विस्तार विधि जोड़कर:tcs.Task

// ImposeAsync, TODO: more overrides
static public Task<TResult> ImposeAsync<TResult>(this Task<TResult> @this)
{
    return @this.ContinueWith(new Func<Task<TResult>, Task<TResult>>(antecedent =>
    {
        Thread thread = null;
        s_tcsTasks.TryGetValue(antecedent, out thread);
        if (Thread.CurrentThread == thread)
        {
            // continue on a pool thread
            return antecedent.ContinueWith(t => t, 
                TaskContinuationOptions.None).Unwrap();
        }
        else
        {
            return antecedent;
        }
    }), TaskContinuationOptions.ExecuteSynchronously).Unwrap();
}

उपयोग:

// library code
var source = new TaskCompletionSource<int>();
var task = source.Task.ImposeAsync();
// ... 

// client code
task.ContinueWith(delegate
{
    Identify();
}, TaskContinuationOptions.ExecuteSynchronously);

// ...
// library code
source.SetResultAsync(123);

यह वास्तव में और ( फिडेल ) दोनों के लिए काम करता है awaitऔरContinueWith प्रतिबिंब हैक्स से मुक्त है।


1
मैं फोन करने वाले को नियंत्रित नहीं करते हैं - मैं उन्हें एक विशिष्ट उपयोग करने के लिए नहीं मिल सकता है जारी रखने के-साथ संस्करण: अगर मैं कर सकता, समस्या पहली जगह में मौजूद नहीं होता
मार्क Gravell

@MarcGravell, मुझे पता नहीं था कि आप कॉलर को नियंत्रित नहीं कर सकते। मैंने एक अपडेट पोस्ट किया कि मैं इससे कैसे निपटूंगा।
noseratio

पुस्तकालय लेखक की दुविधा; पी ध्यान दें कि किसी को वांछित परिणाम प्राप्त करने का एक बहुत सरल और अधिक प्रत्यक्ष तरीका मिला
मार्क ग्रेवेल

4

करने के बजाय क्या

var task = source.Task;

आप इसके बजाय ऐसा करते हैं

var task = source.Task.ContinueWith<Int32>( x => x.Result );

इस प्रकार आप हमेशा एक निरंतरता जोड़ रहे हैं जिसे एसिंक्रोनस रूप से निष्पादित किया जाएगा और फिर इससे कोई फर्क नहीं पड़ता कि ग्राहक उसी संदर्भ में एक निरंतरता चाहते हैं। यह कार्य को करीने से करने की तरह है, है ना?


1
टिप्पणियों में आया (एंड्री देखें); समस्या वहाँ है कि यह है बलों सभी कार्य एक निरंतरता है, जब वे अन्यथा नहीं होती है, जो कुछ है कि दोनों है ContinueWithऔर awaitसामान्य रूप से बचने के लिए कड़ी मेहनत की कोशिश (पहले से ही पूरा आदि को चेक करके) - और के बाद से इस मजबूर होना पड़ा सब कुछ पर श्रमिक, यह वास्तव में स्थिति को बढ़ा देगा। यह एक सकारात्मक विचार है, और मैं आपको इसके लिए धन्यवाद देता हूं: लेकिन यह इस परिदृश्य में मदद नहीं करेगा।
मार्क Gravell

3

यदि आप प्रतिबिंब का उपयोग करने के लिए तैयार हो सकते हैं, तो यह करना चाहिए;

public static class MakeItAsync
{
    static public void TrySetAsync<T>(this TaskCompletionSource<T> source, T result)
    {
        var continuation = typeof(Task).GetField("m_continuationObject", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
        var continuations = (List<object>)continuation.GetValue(source.Task);

        foreach (object c in continuations)
        {
            var option = c.GetType().GetField("m_options", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
            var options = (TaskContinuationOptions)option.GetValue(c);

            options &= ~TaskContinuationOptions.ExecuteSynchronously;
            option.SetValue(c, options);
        }

        source.TrySetResult(result);
    }        
}

यह हैक फ्रेमवर्क के अगले संस्करण में काम करना बंद कर सकता है।
नासिका अनुपात

@ नोसेरियो, सच लेकिन यह अब काम करता है और वे अगले संस्करण में ऐसा करने के लिए एक उचित तरीका भी लागू कर सकते हैं
फ्रेडौ

लेकिन आपको इसकी आवश्यकता क्यों होगी यदि आप बस कर सकते हैं Task.Run(() => tcs.SetResult(result))?
नासिका अनुपात

@Noseratio, मुझे नहीं पता, उस प्रश्न को मार्क :-) से पूछिए, मैं बस ध्वज को हटा रहा हूँ TaskContinuationOptions.ExecuteSynchronously सभी कार्य पर एक TaskCompletionSource से जुड़ा हुआ है जो सुनिश्चित करता है कि वे सभी मुख्य धागे के बजाय थ्रेड का उपयोग करते हैं
Fredou

M_continuationObject हैक वास्तव में धोखा है जो मैं पहले से ही संभावित-समस्याग्रस्त कार्यों की पहचान करने के लिए उपयोग करता हूं - इसलिए यह विचार से परे नहीं है। दिलचस्प है, धन्यवाद। यह अब तक का सबसे उपयोगी दिखने वाला विकल्प है।
मार्क ग्रेवेल

3

अपडेट किया गया , मैंने विरोध के रूप में निपटने के लिए एक अलग उत्तर पोस्ट ContinueWithकिया await(क्योंकि ContinueWithवर्तमान सिंक्रनाइज़ेशन संदर्भ के बारे में परवाह नहीं है)।

आप asynchrony लागू करने के लिए पर जारी रखने बुला से शुरू हो रहा एक गूंगा तुल्यकालन संदर्भ इस्तेमाल कर सकते हैं SetResult/SetCancelled/SetExceptionपर TaskCompletionSource। मेरा मानना ​​है कि वर्तमान सिंक्रनाइज़ेशन संदर्भ (बिंदु पर await tcs.Task) इस तरह के निरंतरता तुल्यकालिक या अतुल्यकालिक बनाने के लिए तय करने के लिए TPL उपयोग मानदंड है।

निम्नलिखित मेरे लिए काम करता है:

if (notifyAsync)
{
    tcs.SetResultAsync(null);
}
else
{
    tcs.SetResult(null);
}

SetResultAsync इस तरह लागू किया गया है:

public static class TaskExt
{
    static public void SetResultAsync<T>(this TaskCompletionSource<T> tcs, T result)
    {
        FakeSynchronizationContext.Execute(() => tcs.SetResult(result));
    }

    // FakeSynchronizationContext
    class FakeSynchronizationContext : SynchronizationContext
    {
        private static readonly ThreadLocal<FakeSynchronizationContext> s_context =
            new ThreadLocal<FakeSynchronizationContext>(() => new FakeSynchronizationContext());

        private FakeSynchronizationContext() { }

        public static FakeSynchronizationContext Instance { get { return s_context.Value; } }

        public static void Execute(Action action)
        {
            var savedContext = SynchronizationContext.Current;
            SynchronizationContext.SetSynchronizationContext(FakeSynchronizationContext.Instance);
            try
            {
                action();
            }
            finally
            {
                SynchronizationContext.SetSynchronizationContext(savedContext);
            }
        }

        // SynchronizationContext methods

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }

        public override void OperationStarted()
        {
            throw new NotImplementedException("OperationStarted");
        }

        public override void OperationCompleted()
        {
            throw new NotImplementedException("OperationCompleted");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            throw new NotImplementedException("Post");
        }

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotImplementedException("Send");
        }
    }
}

SynchronizationContext.SetSynchronizationContext यह उपरि के संदर्भ में बहुत सस्ता है। वास्तव में, WPFDispatcher.BeginInvoke के कार्यान्वयन के लिए एक समान दृष्टिकोण लिया जाता है ।

TPL बिंदु के लक्ष्य सिंक्रनाइज़ेशन संदर्भ की तुलना करता है await है tcs.SetResult। यदि सिंक्रनाइज़ेशन संदर्भ समान है (या दोनों स्थानों पर कोई सिंक्रनाइज़ेशन संदर्भ नहीं है), तो निरंतरता को सीधे, सिंक्रोनाइज़ कहा जाता है। अन्यथा, यह SynchronizationContext.Postलक्ष्य तुल्यकालन संदर्भ, अर्थात, सामान्य awaitव्यवहार का उपयोग करके पंक्तिबद्ध है। यदि यह दृष्टिकोण हमेशा SynchronizationContext.Postव्यवहार करता है (या कोई लक्ष्य सिंक्रनाइज़ेशन संदर्भ नहीं है तो पूल थ्रेड निरंतरता)।

अपडेट , यह काम नहीं करेगा task.ContinueWith, क्योंकिContinueWith वर्तमान सिंक्रनाइज़ेशन संदर्भ के बारे में परवाह नहीं है। हालांकि यह काम करता हैawait task ( फिडेल ) के । इसके लिए काम भी करता है await task.ConfigureAwait(false)

OTOH, इस दृष्टिकोण के लिए काम करता है ContinueWith


प्रलोभन, लेकिन सिंक-संदर्भ को बदलना निश्चित रूप से कॉलिंग एप्लिकेशन को प्रभावित करेगा - उदाहरण के लिए, एक वेब या विंडोज एप्लिकेशन जो कि सिर्फ मेरी लाइब्रेरी का उपयोग करने के लिए होता है उसे सिंक संदर्भ को प्रति सेकंड सैकड़ों बार बदलते हुए नहीं मिलना चाहिए।
मार्क Gravell

@MarcGravell, मैं केवल tcs.SetResultकॉल के दायरे के लिए इसे बदलता हूं । यह थोड़े परमाणु और धागे की सुरक्षित इस तरह से हो जाता है, क्योंकि निरंतरता पर ही होगा या तो एक और पूल धागे या मूल सिंक पर। संदर्भ पर कब्जा कर लिया await tcs.Task। और SynchronizationContext.SetSynchronizationContextखुद बहुत सस्ता है, थ्रेड स्विच से बहुत सस्ता है।
noseratio

हालांकि यह आपकी दूसरी आवश्यकता को पूरा नहीं कर सकता है: उपयोग नहीं करने के लिए ThreadPool। इस समाधान के साथ, टीपीएल वास्तव में उपयोग करेगा ThreadPool, अगर कोई सिंक नहीं था। संदर्भ (या यह मूल डिफ़ॉल्ट एक था) पर await tcs.Task। लेकिन यह मानक TPL व्यवहार है।
noseratio

हम्म् ... चूंकि सिंक-संदर्भ प्रति-सूत्र है, यह वास्तव में व्यवहार्य हो सकता है - और मुझे ctx को चालू रखने की आवश्यकता नहीं होगी - बस इसे कार्यकर्ता धागे के लिए एक बार सेट करें। मैं इसके साथ खेलने के लिए की आवश्यकता होगी
मार्क Gravell

1
@ नोटबंदी आह, सही: यह स्पष्ट नहीं था कि मुख्य बिंदु उन्हें अलग किया जा रहा था । देखेंगे। धन्यवाद।
मार्क ग्रेवल

3

अनुकरण बीच में बंद करें दृष्टिकोण वास्तव में अच्छा लग रहा था, लेकिन TPL अपहरण धागे के लिए नेतृत्व किया कुछ परिस्थितियों में

मैंने तब एक कार्यान्वयन किया था जो निरंतरता वस्तु की जांच करने के समान था , लेकिन किसी भी जांच के लिए निरंतरता के कर रहा था क्योंकि वास्तव में दिए गए कोड को अच्छी तरह से काम करने के लिए वास्तव में बहुत सारे परिदृश्य हैं, लेकिन इसका मतलब है कि यहां तक ​​कि Task.Waitथ्रेड-पूल लुकअप जैसी चीजों का परिणाम है।

अंततः, बहुत सारे और बहुत से आईएल का निरीक्षण करने के बाद, एकमात्र सुरक्षित और उपयोगी परिदृश्य SetOnInvokeMresपरिदृश्य (मैनुअल-रीसेट-इवेंट-स्लिम निरंतरता) है। अन्य परिदृश्यों के बहुत सारे हैं:

  • कुछ सुरक्षित नहीं हैं, और धागा अपहरण का नेतृत्व
  • बाकी उपयोगी नहीं हैं, क्योंकि वे अंततः थ्रेड-पूल की ओर जाते हैं

इसलिए अंत में, मैंने एक गैर-शून्य निरंतरता-वस्तु की जांच करने का विकल्प चुना; यदि यह शून्य है, ठीक है (कोई निरंतरता नहीं है); यदि यह गैर-अशक्त है, SetOnInvokeMresतो इसके लिए विशेष-मामले की जांच - यदि वह यह है: ठीक (सुरक्षित करने के लिए); अन्यथा, थ्रेड-पूल को प्रदर्शन करने दें TrySetComplete, बिना कार्य को बताए कुछ विशेष करने के लिए जैसे कि स्पूफ़िंग एबॉर्ट। दृष्टिकोण Task.Waitका उपयोग करता है SetOnInvokeMres, जो कि विशिष्ट परिदृश्य है जिसे हम वास्तव में कठिन गतिरोध की कोशिश नहीं करना चाहते हैं ।

Type taskType = typeof(Task);
FieldInfo continuationField = taskType.GetField("m_continuationObject", BindingFlags.Instance | BindingFlags.NonPublic);
Type safeScenario = taskType.GetNestedType("SetOnInvokeMres", BindingFlags.NonPublic);
if (continuationField != null && continuationField.FieldType == typeof(object) && safeScenario != null)
{
    var method = new DynamicMethod("IsSyncSafe", typeof(bool), new[] { typeof(Task) }, typeof(Task), true);
    var il = method.GetILGenerator();
    var hasContinuation = il.DefineLabel();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldfld, continuationField);
    Label nonNull = il.DefineLabel(), goodReturn = il.DefineLabel();
    // check if null
    il.Emit(OpCodes.Brtrue_S, nonNull);
    il.MarkLabel(goodReturn);
    il.Emit(OpCodes.Ldc_I4_1);
    il.Emit(OpCodes.Ret);

    // check if is a SetOnInvokeMres - if so, we're OK
    il.MarkLabel(nonNull);
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldfld, continuationField);
    il.Emit(OpCodes.Isinst, safeScenario);
    il.Emit(OpCodes.Brtrue_S, goodReturn);

    il.Emit(OpCodes.Ldc_I4_0);
    il.Emit(OpCodes.Ret);

    IsSyncSafe = (Func<Task, bool>)method.CreateDelegate(typeof(Func<Task, bool>));
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.