मैं अपने जीत फॉर्म थ्रेड में इवेंट कॉलबैक कैसे सुरक्षित करूं?

जब आप किसी ऑब्जेक्ट पर किसी ऑब्जेक्ट पर किसी ऑब्जेक्ट की सदस्यता लेते हैं, तो आप अनिवार्य रूप से ईवेंट कॉल पर अपनी कॉलबैक विधि को नियंत्रित कर रहे हैं। आपको पता नहीं है कि वह ईवेंट स्रोत किसी भिन्न थ्रेड पर ईवेंट ट्रिगर करना चुनता है या नहीं।

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

0
ro fr bn

6 उत्तर

कई साधारण मामलों में, आप MethodInvoker प्रतिनिधि का उपयोग कर सकते हैं और अपना खुद का प्रतिनिधि प्रकार बनाने की आवश्यकता से बच सकते हैं।

0
जोड़ा

मुख्य बिंदु यहां दिए गए हैं:

  1. आप यूआई कंट्रोल कॉल को किसी भिन्न थ्रेड से (फॉर्म के थ्रेड) पर बनाए गए किसी भी थ्रेड से नहीं बना सकते हैं।
  2. आवेदक प्रतिनिधि (यानी, ईवेंट हुक) उसी थ्रेड पर ट्रिगर किए जाते हैं जो ऑब्जेक्ट को फायर कर रहा है।

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

BeginInvoke comes to the rescue. Just use this simple coding model in all your callback methods and you can be sure that things are going to be okay:

private delegate void EventArgsDelegate(object sender, EventArgs ea);

void SomethingHappened(object sender, EventArgs ea)
{
   //
   // Make sure this callback is on the correct thread
   //
   if (this.InvokeRequired)
   {
      this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea });
      return;
   }

   //
   // Do something with the event such as update a control
   //
   textBox1.Text = "Something happened";
}

यह वास्तव में काफी सरल है।

  1. यह कॉलबैक सही धागे पर हुआ है या नहीं, यह जानने के लिए InvokeRequired का उपयोग करें।
  2. यदि नहीं, तो उसी पैरामीटर के साथ सही थ्रेड पर कॉलबैक दोबारा शुरू करें। आप Invoke (अवरुद्ध) या BeginInvoke (गैर-अवरुद्ध) विधियों का उपयोग कर एक विधि को फिर से शुरू कर सकते हैं।
  3. अगली बार फ़ंक्शन कहलाता है, InvokeRequired झूठी रिटर्न देता है क्योंकि हम अब सही धागे पर हैं और सभी खुश हैं।

यह इस समस्या को हल करने और बहु-थ्रेडेड ईवेंट कॉलबैक से आपके फॉर्म को सुरक्षित करने का एक बहुत ही कॉम्पैक्ट तरीका है।

0
जोड़ा
@Supercat ... घटना थ्रॉटलिंग कई अनुप्रयोगों के लिए एक महत्वपूर्ण विषय है, लेकिन यह ऐसा कुछ नहीं है जो यूआई परत का हिस्सा होना चाहिए। उपयुक्त अंतराल पर घटनाओं को प्राप्त करने, कतार, गठबंधन और पुन: भेजने के लिए एक अलग घटना प्रॉक्सी बस बनाई जानी चाहिए। इवेंट बस के किसी भी ग्राहक को पता नहीं होना चाहिए कि घटना थ्रॉटलिंग हो रही है।
जोड़ा लेखक Simon Gillbee, स्रोत
मैं आमतौर पर BeginInvoke को आमंत्रित करने के लिए पसंद करता हूं, लेकिन एक चेतावनी है: किसी को भी कई घटनाओं को कतार में बचना चाहिए। मैं एक UpdateRequired वैरिएबल का उपयोग करता हूं जो एक BeginInvoke होने पर 1 पर सेट होता है, और केवल शून्य होने पर BeginInvoke निष्पादित करता है (Interlocked.Exchange का उपयोग करके)। डिस्प्ले हैंडलर में थोड़ी देर लूप है जो अद्यतन को साफ़ करता है और यदि यह शून्य नहीं था, तो अपडेट और लूप करता है। कुछ मामलों में, एक आवृत्ति को अद्यतन आवृत्ति को आगे बढ़ाने के लिए जोड़ा जाता है (वास्तविक कार्य करने के बजाए प्रगति रीडआउट अपडेट करने में अपना समय व्यतीत करने से रोकने के लिए)
जोड़ा लेखक supercat, स्रोत
मैं उन स्थानों को देख सकता हूं जहां सिंक्रनाइज़ेशन को संभालने के लिए एक अलग "इवेंट बस" उपयोगी हो सकता है, लेकिन कई मामलों में यह एक निश्चित-सूचक सूचक श्रेणी की तरह कुछ अंतिम उपयोगकर्ता के लिए सबसे आसान प्रतीत होता है यदि कक्षा ने केवल न्यूनतम अद्यतन इंटरवल संपत्ति का खुलासा किया हो।
जोड़ा लेखक supercat, स्रोत

मैं इस परिदृश्य में अज्ञात विधियों का बहुत उपयोग करता हूं:

void SomethingHappened(object sender, EventArgs ea)
{
   MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
   InvokeRequired ? Invoke( del ) : del(); 
}
0
जोड़ा

मैं इस विषय के लिए थोड़ा देर हो चुकी हूं, लेकिन आप घटना-आधारित असीमित पैटर्न । जब सही तरीके से कार्यान्वित किया जाता है, तो यह गारंटी देता है कि ईवेंट हमेशा यूआई थ्रेड से उठाए जाते हैं।

यहां एक संक्षिप्त उदाहरण दिया गया है जो केवल एक समवर्ती आमंत्रण की अनुमति देता है; एकाधिक आमंत्रण / घटनाओं का समर्थन करने के लिए थोड़ा और अधिक नलसाजी की आवश्यकता होती है।

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class MainForm : Form
    {
        private TypeWithAsync _type;

        [STAThread()]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new MainForm());
        }

        public MainForm()
        {
            _type = new TypeWithAsync();
            _type.DoSomethingCompleted += DoSomethingCompleted;

            var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill };

            var btn = new Button() { Text = "Synchronous" };
            btn.Click += SyncClick;
            panel.Controls.Add(btn);

            btn = new Button { Text = "Asynchronous" };
            btn.Click += AsyncClick;
            panel.Controls.Add(btn);

            Controls.Add(panel);
        }

        private void SyncClick(object sender, EventArgs e)
        {
            int value = _type.DoSomething();
            MessageBox.Show(string.Format("DoSomething() returned {0}.", value));
        }

        private void AsyncClick(object sender, EventArgs e)
        {
            _type.DoSomethingAsync();
        }

        private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e)
        {
            MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value));
        }
    }

    class TypeWithAsync
    {
        private AsyncOperation _operation;

        // synchronous version of method
        public int DoSomething()
        {
            Thread.Sleep(5000);
            return 27;
        }

        // async version of method
        public void DoSomethingAsync()
        {
            if (_operation != null)
            {
                throw new InvalidOperationException("An async operation is already running.");
            }

            _operation = AsyncOperationManager.CreateOperation(null);
            ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore);
        }

        // wrapper used by async method to call sync version of method, matches WaitCallback so it
        // can be queued by the thread pool
        private void DoSomethingAsyncCore(object state)
        {
            int returnValue = DoSomething();
            var e = new DoSomethingCompletedEventArgs(returnValue);
            _operation.PostOperationCompleted(RaiseDoSomethingCompleted, e);
        }

        // wrapper used so async method can raise the event; matches SendOrPostCallback
        private void RaiseDoSomethingCompleted(object args)
        {
            OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args);
        }

        private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e)
        {
            var handler = DoSomethingCompleted;

            if (handler != null) { handler(this, e); }
        }

        public EventHandler DoSomethingCompleted;
    }

    public class DoSomethingCompletedEventArgs : EventArgs
    {
        private int _value;

        public DoSomethingCompletedEventArgs(int value)
            : base()
        {
            _value = value;
        }

        public int Value
        {
            get { return _value; }
        }
    }
}
0
जोड़ा
मुझे लगता है कि यह कहने के लिए थोड़ा भ्रामक है कि 'यह गारंटी देता है कि ईवेंट हमेशा यूआई थ्रेड से उठाए जाते हैं'। क्या यह कहना अधिक सटीक नहीं होगा कि यह सुनिश्चित करता है कि ईवेंट हैंडलर को उसी सिंक्रनाइज़ेशन कॉन्टेक्स्ट / थ्रेड पर निष्पादित किया गया है जिस पर कार्य बनाया गया था? (जो यूआई थ्रेड / सिंक्रनाइज़ेशन कॉन्टेक्स्ट नहीं हो सकता है)
जोड़ा लेखक jspaey, स्रोत

साइमन के कोड को थोड़ा सा सरल बनाने के लिए, आप जेनेरिक एक्शन प्रतिनिधि में निर्मित का उपयोग कर सकते हैं। यह आपके कोड को उन प्रतिनिधि प्रकारों के समूह के साथ मिर्च कर बचाता है जिनकी आपको वास्तव में आवश्यकता नहीं है। साथ ही, .NET 3.5 में उन्होंने इनवॉक विधि में पैरा पैरामीटर जोड़ा, इसलिए आपको अस्थायी सरणी को परिभाषित करने की आवश्यकता नहीं है।

void SomethingHappened(object sender, EventArgs ea)
{
   if (InvokeRequired)
   {
      Invoke(new Action(SomethingHappened), sender, ea);
      return;
   }

   textBox1.Text = "Something happened";
}
0
जोड़ा

आलसी प्रोग्रामर के रूप में, मेरे पास ऐसा करने का एक बहुत आलसी तरीका है।

मैं बस इतना करता हूं।

private void DoInvoke(MethodInvoker del) {
    if (InvokeRequired) {
        Invoke(del);
    } else {
        del();
    }
}
//example of how to call it
private void tUpdateLabel(ToolStripStatusLabel lbl, String val) {
    DoInvoke(delegate { lbl.Text = val; });
}

आप अपने फ़ंक्शन के अंदर DoInvoke को रेखांकित कर सकते हैं या आपके लिए गंदे काम करने के लिए इसे अलग-अलग फ़ंक्शन के भीतर छुपा सकते हैं।

बस ध्यान रखें कि आप सीधे DoInvoke विधि में फ़ंक्शन पास कर सकते हैं।

private void directPass() {
    DoInvoke(this.directInvoke);
}
private void directInvoke() {
    textLabel.Text = "Directly passed.";
}
0
जोड़ा
मैं आलसी प्रोग्रामिंग के लिए सभी हूं :) यदि आप .NET 3.5 या उच्चतर का उपयोग कर रहे हैं, तो आप लैम्बडा एक्सप्रेशन के साथ action या action का उपयोग कर सकते हैं: <�कोड> डोनवोक (() => textLabel.Text = "कुछ")
जोड़ा लेखक Simon Gillbee, स्रोत