عملية الترابط غير صالحة:يتم الوصول إلى التحكم من سلسلة رسائل غير السلسلة التي تم إنشاؤها عليها

StackOverflow https://stackoverflow.com/questions/142003

سؤال

لدي سيناريو.(نماذج ويندوز، C#، .NET)

  1. يوجد نموذج رئيسي يستضيف بعض تحكم المستخدم.
  2. يقوم عنصر تحكم المستخدم ببعض عمليات البيانات الثقيلة، بحيث إذا قمت بالاتصال مباشرة بـ UserControl_Load الطريقة التي تصبح بها واجهة المستخدم غير مستجيبة طوال مدة تنفيذ طريقة التحميل.
  3. للتغلب على هذا أقوم بتحميل البيانات على موضوع مختلف (أحاول تغيير الكود الموجود بأقل قدر ممكن)
  4. لقد استخدمت مؤشر ترابط عامل في الخلفية والذي سيقوم بتحميل البيانات وعند الانتهاء سيُعلم التطبيق بأنه قد قام بعمله.
  5. الآن جاءت مشكلة حقيقية.تم إنشاء كل واجهة المستخدم (النموذج الرئيسي وعناصر تحكم المستخدم التابعة له) على سلسلة المحادثات الرئيسية الأساسية.في طريقة LOAD الخاصة بـ usercontrol، أقوم بجلب البيانات بناءً على قيم بعض عناصر التحكم (مثل مربع النص) في userControl.

سيبدو الكود الكاذب كما يلي:

الكود 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

الاستثناء الذي قدمه كان

عملية الترابط غير صالحة:يتم الوصول إلى التحكم من سلسلة رسائل غير السلسلة التي تم إنشاؤها عليها.

لمعرفة المزيد حول هذا الأمر، قمت بالبحث على Google وظهر اقتراح مثل استخدام الكود التالي

الكود 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

لكن لكن لكن...يبدو أنني عدت إلى المربع الأول.يصبح التطبيق مرة أخرى غير مستجدات.يبدو أن ذلك يرجع إلى تنفيذ السطر رقم 1 إذا كان الشرط.تتم مهمة التحميل مرة أخرى بواسطة الخيط الأصلي وليس الخيط الثالث الذي قمت بإنشائه.

لا أعرف هل فهمت هذا الأمر بشكل صحيح أم خطأ.أنا جديد على الخيوط.

كيف يمكنني حل هذه المشكلة وأيضًا ما هو تأثير تنفيذ السطر رقم 1 في حالة الحظر؟

الوضع هو هذا:أريد تحميل البيانات إلى متغير عالمي بناءً على قيمة عنصر التحكم.لا أريد تغيير قيمة عنصر التحكم من مؤشر الترابط الفرعي.لن أفعل ذلك أبدًا من موضوع فرعي.

لذلك يتم الوصول إلى القيمة فقط بحيث يمكن جلب البيانات المقابلة من قاعدة البيانات.

هل كانت مفيدة؟

المحلول

حسب تعليق تحديث Prerak K (منذ حذف):

أعتقد أنني لم أطرح السؤال بشكل صحيح.

الوضع هو هذا:أريد تحميل البيانات إلى متغير عالمي بناءً على قيمة عنصر التحكم.لا أريد تغيير قيمة عنصر التحكم من مؤشر الترابط الفرعي.لن أفعل ذلك أبدًا من موضوع فرعي.

لذلك يتم الوصول إلى القيمة فقط بحيث يمكن جلب البيانات المقابلة من قاعدة البيانات.

يجب أن يبدو الحل الذي تريده بعد ذلك كما يلي:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

قم بمعالجتك الجادة في موضوع منفصل قبل حاولت التبديل مرة أخرى إلى مؤشر ترابط عنصر التحكم.على سبيل المثال:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

نصائح أخرى

نموذج الترابط في واجهة المستخدم

يرجى قراءة نموذج الخيوط في تطبيقات واجهة المستخدم من أجل فهم المفاهيم الأساسية.ينتقل الرابط إلى الصفحة التي تصف نموذج ترابط WPF.ومع ذلك، يستخدم Windows Forms نفس الفكرة.

موضوع واجهة المستخدم

  • هناك مؤشر ترابط واحد فقط (مؤشر ترابط واجهة المستخدم) مسموح له بالوصول إليه System.Windows.Forms.Control وأعضائها من الفئات الفرعية.
  • محاولة الوصول إلى عضو System.Windows.Forms.Control من مؤشر ترابط مختلف عن مؤشر ترابط واجهة المستخدم سوف يتسبب في حدوث استثناء عبر مؤشر الترابط.
  • نظرًا لوجود مؤشر ترابط واحد فقط، يتم وضع كافة عمليات واجهة المستخدم في قائمة الانتظار كعناصر عمل في مؤشر الترابط هذا:

enter image description here

  • إذا لم يكن هناك عمل لمؤشر ترابط واجهة المستخدم، فهناك فجوات خاملة يمكن استخدامها بواسطة حوسبة غير مرتبطة بواجهة المستخدم.
  • من أجل استخدام الثغرات المذكورة استخدام System.Windows.Forms.Control.Invoc أو System.Windows.Forms.Control.BeginInvoc طُرق:

enter image description here

أساليب BeginInvoc و Invoc

  • يجب أن يكون حمل الحوسبة للطريقة التي يتم استدعاؤها صغيرًا بالإضافة إلى الحمل الزائد لطرق معالج الأحداث لأنه يتم استخدام مؤشر ترابط واجهة المستخدم هناك - وهو نفس المسؤول عن معالجة إدخال المستخدم.بغض النظر عما إذا كان هذا System.Windows.Forms.Control.Invoc أو System.Windows.Forms.Control.BeginInvoc.
  • لإجراء عملية حسابية باهظة الثمن، استخدم دائمًا خيطًا منفصلاً.منذ .NET 2.0 تطبيقBackgroundWorker مخصص لأداء عمليات الحوسبة باهظة الثمن في Windows Forms.ولكن في الحلول الجديدة، يجب عليك استخدام نمط الانتظار غير المتزامن كما هو موضح هنا.
  • يستخدم System.Windows.Forms.Control.Invoc أو System.Windows.Forms.Control.BeginInvoc طرق فقط لتحديث واجهة المستخدم.إذا كنت تستخدمها لإجراء عمليات حسابية ثقيلة، فسيحظر تطبيقك ما يلي:

enter image description here

يستحضر

enter image description here

BeginInvoc

enter image description here

حل الكود

قراءة الإجابات على السؤال كيفية تحديث واجهة المستخدم الرسومية من موضوع آخر في C#؟.بالنسبة لـ C# 5.0 و.NET 4.5، الحل الموصى به هو هنا.

أنت تريد فقط استخدام Invoc أو BeginInvoce للحد الأدنى من العمل المطلوب لتغيير واجهة المستخدم.يجب تنفيذ طريقتك "الثقيلة" على مؤشر ترابط آخر (على سبيل المثال.عبرBackgroundWorker) ولكن بعد ذلك استخدم Control.Invoc/Control.BeginInvoce فقط لتحديث واجهة المستخدم.بهذه الطريقة سيكون مؤشر ترابط واجهة المستخدم الخاص بك حرًا في التعامل مع أحداث واجهة المستخدم وما إلى ذلك.

انظر بلدي مقالة خيوط ل مثال WinForms - على الرغم من أن المقال قد تم كتابته قبل وصول BackgroundWorker إلى مكان الحادث، وأخشى أنني لم أقم بتحديثه في هذا الصدد.يقوم تطبيق BackgroundWorker بتبسيط عملية رد الاتصال قليلاً.

لقد واجهت هذه المشكلة مع FileSystemWatcher ووجدت أن الكود التالي حل المشكلة:

fsw.SynchronizingObject = this

يستخدم عنصر التحكم بعد ذلك كائن النموذج الحالي للتعامل مع الأحداث، وبالتالي سيكون على نفس مؤشر الترابط.

أعرف أن الوقت قد فات الآن.ولكن حتى اليوم إذا كنت تواجه مشكلة في الوصول إلى عناصر التحكم في الخيط المتقاطع؟هذه أقصر إجابة حتى الآن :P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

هذه هي الطريقة التي يمكنني بها الوصول إلى أي عنصر تحكم في النموذج من سلسلة رسائل.

عناصر التحكم في .NET ليست آمنة بشكل عام.وهذا يعني أنه لا ينبغي عليك الوصول إلى عنصر تحكم من سلسلة رسائل غير تلك التي تعيش فيها.للتغلب على هذا، عليك أن يستحضر عنصر التحكم، وهو ما تحاول عينتك الثانية القيام به.

ومع ذلك، في حالتك، كل ما فعلته هو إعادة الطريقة طويلة الأمد إلى سلسلة المحادثات الرئيسية.وبطبيعة الحال، هذا ليس حقا ما تريد القيام به.تحتاج إلى إعادة التفكير في هذا الأمر قليلًا بحيث يكون كل ما تفعله في الموضوع الرئيسي هو تعيين خاصية سريعة هنا وهناك.

أجد أن رمز التحقق والاستدعاء الذي يجب تناثره في جميع الأساليب المتعلقة بالنماذج مطول للغاية وغير ضروري.إليك طريقة تمديد بسيطة تتيح لك التخلص منها تمامًا:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

وبعد ذلك يمكنك القيام بذلك ببساطة:

textbox1.Invoke(t => t.Text = "A");

لا مزيد من العبث - بسيط.

الحل الأنظف (والمناسب) لمشكلات ترابط واجهة المستخدم هو استخدام SynchronizationContext، راجع مزامنة المكالمات مع واجهة المستخدم في تطبيق متعدد الخيوط المادة، فإنه يشرح ذلك بشكل جيد جدا.

مظهر جديد باستخدام Async/Await وعمليات الاسترجاعات.ما عليك سوى سطر واحد من التعليمات البرمجية إذا احتفظت بطريقة الامتداد في مشروعك.

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

يمكنك إضافة أشياء أخرى إلى التابع Extension مثل تغليفه في عبارة Try/Catch، مما يسمح للمتصل بإخباره بالنوع الذي سيعود بعد الانتهاء، وهو رد اتصال استثنائي للمتصل:

إضافة محاولة الالتقاط وتسجيل الاستثناءات التلقائية ورد الاتصال

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

أنت بحاجة إلى إلقاء نظرة على مثال Backworkworker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspxوخاصة كيفية تفاعلها مع طبقة واجهة المستخدم.بناءً على منشورك، يبدو أن هذا يجيب على مشكلاتك.

اتبع أبسط طريقة (في رأيي) لتعديل الكائنات من موضوع آخر:

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

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

لقد وجدت حاجة لذلك أثناء برمجة وحدة تحكم تطبيق أحادية اللمس تعمل بنظام iOS-Phone في مشروع نموذج أولي لـ winforms في الاستوديو المرئي خارج xamarin stuidio.تفضيلًا للبرمجة باستخدام VS بدلاً من xamarin studio قدر الإمكان، أردت أن يتم فصل وحدة التحكم تمامًا عن إطار عمل الهاتف.وبهذه الطريقة، سيكون تطبيق ذلك على أطر عمل أخرى مثل Android وWindows Phone أسهل بكثير للاستخدامات المستقبلية.

كنت أرغب في حل حيث يمكن لواجهة المستخدم الرسومية الاستجابة للأحداث دون تحمل عبء التعامل مع رمز تبديل الترابط المتقاطع خلف كل نقرة على الزر.دع وحدة التحكم في الفصل تتعامل مع ذلك بشكل أساسي للحفاظ على رمز العميل بسيطًا.من الممكن أن يكون لديك العديد من الأحداث على واجهة المستخدم الرسومية حيث سيكون من الأفضل التعامل معها في مكان واحد في الفصل الدراسي.أنا لست خبيرًا في العديد من المواضيع، اسمحوا لي أن أعرف إذا كان هذا معيبًا.

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

نموذج واجهة المستخدم الرسومية غير مدرك أن وحدة التحكم تقوم بتشغيل مهام غير متزامنة.

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

هذه ليست الطريقة الموصى بها لحل هذا الخطأ ولكن يمكنك منعه بسرعة، وسوف يقوم بالمهمة.أفضّل هذا بالنسبة للنماذج الأولية أو العروض التوضيحية.يضيف

CheckForIllegalCrossThreadCalls = false

في Form1() البناء .

إليك طريقة بديلة إذا لم يكن الكائن الذي تعمل معه موجودًا

(InvokeRequired)

يعد هذا مفيدًا إذا كنت تعمل مع النموذج الرئيسي في فئة أخرى غير النموذج الرئيسي مع كائن موجود في النموذج الرئيسي، ولكن لا يحتوي على InvineRequired

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

إنه يعمل بنفس الطريقة المذكورة أعلاه، ولكن هناك طريقة مختلفة إذا لم يكن لديك كائن يتطلب الاستدعاء، ولكن لديك إمكانية الوصول إلى النموذج الرئيسي

على نفس المنوال مثل الإجابات السابقة ، ولكن إضافة قصيرة جدًا تسمح باستخدام جميع خصائص التحكم دون استثناء استثناء استدعاء مؤشر الترابط.

الطريقة المساعدة

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

استخدام العينة

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));

على سبيل المثال للحصول على النص من عنصر التحكم في مؤشر ترابط واجهة المستخدم:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function

نفس السؤال :كيفية تحديث واجهة المستخدم الرسومية من موضوع آخر في ج

بطريقتين:

  1. قم بإرجاع القيمة في e.result واستخدمها لتعيين قيمة مربع النص الخاص بك في حدثbackgroundWorker_RunWorkerCompleted

  2. قم بتعريف بعض المتغيرات للاحتفاظ بهذا النوع من القيم في فئة منفصلة (والتي ستعمل كحامل بيانات).قم بإنشاء مثيل ثابت لهذه الفئة ويمكنك الوصول إليها عبر أي موضوع.

مثال:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}

العمل ذ؛// أعلن داخل الفصل

label1.Invoc(y=()=>label1.Text="text");

ببساطة استخدم هذا:

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });

هناك خياران لعمليات الخيط المتقاطع.

Control.InvokeRequired Property 

والثاني هو الاستخدام

SynchronizationContext Post Method

يعد Control.InvineRequired مفيدًا فقط عند استخدام عناصر التحكم العاملة الموروثة من فئة التحكم بينما يمكن استخدام SynchronizationContext في أي مكان.بعض المعلومات المفيدة هي على النحو التالي الروابط

Cross Thread Update UI | .شبكة

Cross Thread Update UI باستخدام SynchronizationContext | .شبكة

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top