Hoạt động xuyên luồng không hợp lệ: Kiểm soát truy cập từ một luồng khác với luồng được tạo trên


584

Tôi có một kịch bản. (Biểu mẫu Windows, C #, .NET)

  1. Có một hình thức chính lưu trữ một số điều khiển người dùng.
  2. Điều khiển người dùng thực hiện một số thao tác dữ liệu nặng, như vậy nếu tôi gọi trực tiếp UserControl_Loadphương thức thì UI sẽ không phản hồi trong thời gian thực hiện phương thức tải.
  3. Để khắc phục điều này, tôi tải dữ liệu trên các luồng khác nhau (cố gắng thay đổi mã hiện có ít nhất có thể)
  4. Tôi đã sử dụng một luồng công nhân nền sẽ tải dữ liệu và khi hoàn thành sẽ thông báo cho ứng dụng rằng nó đã hoàn thành công việc.
  5. Bây giờ đến một vấn đề thực sự. Tất cả giao diện người dùng (biểu mẫu chính và các điều khiển người dùng con của nó) đã được tạo trên luồng chính chính. Trong phương thức LOAD của điều khiển người dùng, tôi đang tìm nạp dữ liệu dựa trên các giá trị của một số điều khiển (như hộp văn bản) trên userControl.

Mã giả sẽ trông như thế này:

MÃ 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.
    }
}

Ngoại lệ mà nó đưa ra là

Hoạt động xuyên luồng không hợp lệ: Kiểm soát truy cập từ một luồng khác với luồng được tạo.

Để biết thêm về điều này, tôi đã làm một số việc và một gợi ý được đưa ra như sử dụng mã sau đây

MÃ 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
    }
}

NHƯNG NHƯNG NHƯNG ... có vẻ như tôi trở lại hình vuông. Ứng dụng một lần nữa trở nên không phản hồi. Có vẻ là do việc thực hiện dòng # 1 nếu có điều kiện. Tác vụ tải được thực hiện lại bởi luồng gốc chứ không phải thứ ba mà tôi sinh ra.

Tôi không biết liệu tôi nhận ra điều này đúng hay sai. Tôi mới bắt đầu xâu chuỗi.

Làm cách nào để giải quyết vấn đề này và hiệu quả của việc thực thi Dòng # 1 là gì?

Tình huống là thế này : Tôi muốn tải dữ liệu vào một biến toàn cục dựa trên giá trị của điều khiển. Tôi không muốn thay đổi giá trị của điều khiển từ luồng con. Tôi sẽ không làm điều đó bao giờ từ một chủ đề con.

Vì vậy, chỉ truy cập giá trị để dữ liệu tương ứng có thể được tìm nạp từ cơ sở dữ liệu.


Đối với trường hợp cụ thể của tôi về lỗi này, tôi thấy cách giải quyết là sử dụng BackgroundWorker trên biểu mẫu để xử lý các phần thâm dụng dữ liệu của mã. (tức là đặt tất cả các mã vấn đề thành phương pháp backgroundWorker1_DoWork () và gọi nó qua backgroundWorker1.RunWorkerAsync ()) ... Hai nguồn chỉ cho tôi trong đúng hướng: stackoverflow.com/questions/4806742/... youtube.com/ xem? v = MLrrbG6V1zM
Giollia

Câu trả lời:


433

Theo nhận xét cập nhật của Prerak K (kể từ khi xóa):

Tôi đoán tôi đã không trình bày câu hỏi đúng.

Tình huống là thế này: Tôi muốn tải dữ liệu vào một biến toàn cục dựa trên giá trị của điều khiển. Tôi không muốn thay đổi giá trị của điều khiển từ luồng con. Tôi sẽ không làm điều đó bao giờ từ một chủ đề con.

Vì vậy, chỉ truy cập giá trị để dữ liệu tương ứng có thể được tìm nạp từ cơ sở dữ liệu.

Giải pháp bạn muốn sau đó sẽ giống như:

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

Thực hiện xử lý nghiêm túc của bạn trong luồng riêng biệt trước khi bạn cố gắng chuyển trở lại luồng của điều khiển. Ví dụ:

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;
        }
    }
}

1
Đã được một thời gian kể từ khi tôi thực hiện lập trình C #, nhưng dựa trên bài viết MSDN và kiến ​​thức chắp vá của tôi, có vẻ như nó.
Jeff Hubbard

1
Sự khác biệt là, BeginInvoke () không đồng bộ trong khi Invoke () chạy đồng bộ. stackoverflow.com/questions/229554/
hy

178

Mô hình luồng trong UI

Vui lòng đọc Mô hình luồng trong các ứng dụng UI để hiểu các khái niệm cơ bản. Liên kết điều hướng đến trang mô tả mô hình luồng WPF. Tuy nhiên, Windows Forms sử dụng cùng một ý tưởng.

Giao diện người dùng

  • Chỉ có một luồng (luồng UI), được phép truy cập System.Windows.Forms.Control và các thành viên lớp con của nó.
  • Cố gắng truy cập thành viên của System.Windows.Forms. Kiểm soát từ luồng khác với luồng UI sẽ gây ra ngoại lệ luồng chéo.
  • Vì chỉ có một luồng, tất cả các thao tác UI được xếp hàng dưới dạng các mục công việc vào luồng đó:

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

Phương thức BeginInvoke và Gọi

  • Chi phí tính toán của phương thức được gọi phải nhỏ cũng như chi phí tính toán của các phương thức xử lý sự kiện vì luồng UI được sử dụng ở đó - chính là phần chịu trách nhiệm xử lý đầu vào của người dùng. Bất kể nếu đây là System.Windows.Forms.Control.Invoke hoặc System.Windows.Forms.Control.BeginInvoke .
  • Để thực hiện tính toán hoạt động đắt tiền luôn luôn sử dụng luồng riêng biệt. Vì .NET 2.0 BackgroundWorker được dành riêng để thực hiện tính toán các hoạt động đắt tiền trong Windows Forms. Tuy nhiên, trong các giải pháp mới, bạn nên sử dụng mẫu async-await như được mô tả ở đây .
  • Sử dụng System.Windows.Forms.Control.Invoke hoặc System.Windows.Forms.Control.BeginInvoke phương pháp duy nhất để cập nhật giao diện người dùng. Nếu bạn sử dụng chúng cho các tính toán nặng, ứng dụng của bạn sẽ chặn:

nhập mô tả hình ảnh ở đây

Gọi

nhập mô tả hình ảnh ở đây

Bắt đầu

nhập mô tả hình ảnh ở đây

Giải pháp mã

Đọc câu trả lời cho câu hỏi Làm thế nào để cập nhật GUI từ một luồng khác trong C #? . Đối với C # 5.0 và .NET 4.5, giải pháp được đề xuất là ở đây .



72

Bạn chỉ muốn sử dụng Invokehoặc BeginInvokecho phần công việc tối thiểu cần thiết để thay đổi UI. Phương thức "nặng" của bạn sẽ thực thi trên một luồng khác (ví dụ thông qua BackgroundWorker) nhưng sau đó sử dụng Control.Invoke/ Control.BeginInvokechỉ để cập nhật giao diện người dùng. Bằng cách đó, luồng UI của bạn sẽ được tự do xử lý các sự kiện UI, v.v.

Xem bài viết phân luồng của tôi để biết ví dụ về WinForms - mặc dù bài viết đã được viết trước khi BackgroundWorkerđến hiện trường và tôi sợ rằng tôi đã không cập nhật nó về mặt đó. BackgroundWorkerchỉ đơn giản hóa việc gọi lại một chút.


ở đây trong điều kiện này của tôi. tôi thậm chí không thay đổi giao diện người dùng. Tôi chỉ truy cập các giá trị hiện tại của nó từ luồng con. bất kỳ đề xuất nào hw để thực hiện
Prerak K

1
Bạn vẫn cần phải sắp xếp theo luồng UI thậm chí chỉ để truy cập các thuộc tính. Nếu phương thức của bạn không thể tiếp tục cho đến khi giá trị được truy cập, bạn có thể sử dụng một đại biểu trả về giá trị. Nhưng có, đi qua chủ đề UI.
Jon Skeet

Xin chào Jon, tôi tin rằng bạn đang hướng tôi đến đúng hướng. Có tôi cần giá trị mà không có nó tôi không thể tiến hành thêm. Xin vui lòng bạn có thể gợi ý về điều đó 'Sử dụng một đại biểu trả về giá trị'. Cảm ơn
Prerak K

1
Sử dụng một ủy nhiệm như Func <string>: string text = textbox1.Invoke ((Func <string>) () => textbox1.Text); (Giả sử bạn đang sử dụng C # 3.0 - nếu không bạn có thể sử dụng một phương thức ẩn danh.)
Jon Skeet

45

Tôi biết bây giờ đã quá muộn. Tuy nhiên ngay cả ngày hôm nay nếu bạn gặp khó khăn khi truy cập điều khiển luồng chéo? Đây là câu trả lời ngắn nhất cho đến ngày: P

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

Đây là cách tôi truy cập bất kỳ điều khiển biểu mẫu từ một chủ đề.


1
Điều này mang lại cho tôi Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Tôi đã giải quyết nó ở đây
rupweb

42

Tôi đã có vấn đề này với FileSystemWatchervà thấy rằng đoạn mã sau đã giải quyết vấn đề:

fsw.SynchronizingObject = this

Điều khiển sau đó sử dụng đối tượng biểu mẫu hiện tại để xử lý các sự kiện và do đó sẽ nằm trên cùng một luồng.


2
Điều này đã cứu thịt xông khói của tôi. Trong VB.NET tôi đã sử dụng.SynchronizingObject = Me
mã hóa

20

Tôi thấy mã kiểm tra và yêu cầu cần được đặt trong tất cả các phương thức liên quan đến các biểu mẫu quá dài dòng và không cần thiết. Đây là một phương thức mở rộng đơn giản cho phép bạn loại bỏ hoàn toàn:

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);
    }
}

Và sau đó bạn có thể chỉ cần làm điều này:

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

Không còn lộn xộn xung quanh - đơn giản.


't' ở đây là gì
Rawat

@Raw ttrong trường hợp này sẽ là textbox1- nó được thông qua như một cuộc tranh cãi
Rob

17

Điều khiển trong .NET thường không phải là chủ đề an toàn. Điều đó có nghĩa là bạn không nên truy cập điều khiển từ một luồng khác ngoài luồng mà nó sống. Để giải quyết vấn đề này, bạn cần phải gọi điều khiển, đây là điều mà mẫu thứ 2 của bạn đang cố gắng.

Tuy nhiên, trong trường hợp của bạn, tất cả những gì bạn đã làm là chuyển phương thức chạy dài trở lại luồng chính. Tất nhiên, đó không thực sự là những gì bạn muốn làm. Bạn cần suy nghĩ lại một chút để tất cả những gì bạn đang làm trên luồng chính đang thiết lập một thuộc tính nhanh ở đây và đó.



10

Giao diện mới sử dụng Async / Await và callbacks. Bạn chỉ cần một dòng mã nếu bạn giữ phương thức mở rộng trong dự án của mình.

/// <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;
    }
}

Bạn có thể thêm những thứ khác vào phương thức Tiện ích mở rộng, chẳng hạn như gói nó trong câu lệnh Thử / Bắt, cho phép người gọi cho nó biết loại nào sẽ trả về sau khi hoàn thành, một cuộc gọi lại ngoại lệ cho người gọi:

Thêm Thử bắt, Ghi nhật ký ngoại lệ tự động và CallBack

    /// <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
        }
    }

10

Đây không phải là cách được đề xuất để giải quyết lỗi này nhưng bạn có thể nhanh chóng khắc phục nó, nó sẽ thực hiện công việc. Tôi thích điều này cho các nguyên mẫu hoặc bản demo. thêm vào

CheckForIllegalCrossThreadCalls = false

trong nhà Form1()xây dựng.


9

Thực hiện theo cách đơn giản nhất (theo ý kiến ​​của tôi) để sửa đổi các đối tượng từ một luồng khác:

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;
        }
    }
}

Đơn giản ! cảm ơn .
Ali Esmaeili


7

Tôi thấy cần một điều này trong khi lập trình bộ điều khiển ứng dụng monotouch iOS-Phone trong một dự án nguyên mẫu winforms của studio trực quan bên ngoài xamarin stuidio. Thích lập trình trong VS hơn xamarin studio càng nhiều càng tốt, tôi muốn bộ điều khiển được tách rời hoàn toàn khỏi khung điện thoại. Cách này thực hiện điều này cho các khung khác như Android và Windows Phone sẽ dễ dàng hơn nhiều cho việc sử dụng trong tương lai.

Tôi muốn một giải pháp trong đó GUI có thể phản hồi các sự kiện mà không phải chịu trách nhiệm xử lý mã chuyển đổi luồng chéo sau mỗi lần nhấp vào nút. Về cơ bản hãy để trình điều khiển lớp xử lý việc đó để giữ cho mã máy khách đơn giản. Bạn có thể có nhiều sự kiện trên GUI trong đó nếu bạn có thể xử lý nó ở một nơi trong lớp sẽ sạch hơn. Tôi không phải là một chuyên gia đa năng, hãy cho tôi biết nếu điều này là thiếu sót.

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");
    }
}

Biểu mẫu GUI không biết bộ điều khiển đang chạy các tác vụ không đồng bộ.

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);
            }
        }
    }
}

6

Đây là một cách khác nếu đối tượng bạn đang làm việc không có

(InvokeRequired)

Điều này hữu ích nếu bạn đang làm việc với biểu mẫu chính trong một lớp khác với biểu mẫu chính với một đối tượng ở dạng chính, nhưng không có InvokeRequired

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;
}

Nó hoạt động tương tự như trên, nhưng nó là một cách tiếp cận khác nếu bạn không có đối tượng với yêu cầu, nhưng có quyền truy cập vào MainForm


5

Cùng một dòng với các câu trả lời trước đó, nhưng một bổ sung rất ngắn cho phép sử dụng tất cả các thuộc tính Điều khiển mà không có ngoại lệ yêu cầu chuỗi chéo.

Phương pháp trợ giúp

/// <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;
}

Sử dụng mẫu

// 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;
}


5

Ví dụ: để lấy văn bản từ một Điều khiển của luồng UI:

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

3

Câu hỏi tương tự: how-to-update-the-gui-from-other-thread-in-c

Hai lối:

  1. Trả về giá trị trong e.result và sử dụng nó để đặt giá trị hộp văn bản yout trong sự kiện backgroundWorker_RunWorkerCompleted

  2. Khai báo một số biến để giữ các loại giá trị này trong một lớp riêng (sẽ hoạt động như người giữ dữ liệu). Tạo ví dụ tĩnh của lớp này, bạn có thể truy cập nó qua bất kỳ luồng nào.

Thí dụ:

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";
    }
}

2

Đơn giản chỉ cần sử dụng này:

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

0

Hành động y; // khai báo bên trong lớp

nhãn1.Invoke (y = () => nhãn1.Text = "văn bản");


0

Cách đơn giản và có thể sử dụng lại để giải quyết vấn đề này.

Phương pháp mở rộng

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

Sử dụng mẫu

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

-3

Có hai tùy chọn cho các hoạt động chủ đề chéo.

Control.InvokeRequired Property 

và thứ hai là sử dụng

SynchronizationContext Post Method

Control.InvokeRequired chỉ hữu ích khi các điều khiển làm việc được kế thừa từ lớp Control trong khi SyncizationContext có thể được sử dụng ở bất cứ đâu. Một số thông tin hữu ích như các liên kết sau

Cập nhật giao diện người dùng chéo | .Mạng lưới

Giao diện người dùng cập nhật chủ đề chéo bằng cách sử dụng SyncizationContext | .Mạng lưới

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.