Invoke hoặc BeginInvoke không thể được gọi trên một điều khiển cho đến khi xử lý cửa sổ đã được tạo


81

Tôi có phương pháp mở rộng SafeInvoke Control tương tự như phương pháp mà Greg D thảo luận ở đây (trừ kiểm tra IsHandleCreate).

Tôi đang gọi nó từ một System.Windows.Forms.Formnhư sau:

public void Show(string text) {
    label.SafeInvoke(()=>label.Text = text);
    this.Show();
    this.Refresh();
}

Đôi khi (cuộc gọi này có thể đến từ nhiều chuỗi khác nhau), điều này dẫn đến lỗi sau:

System.InvalidOperationException xảy ra

Message= "Không thể gọi hoặc BeginInvoke trên một điều khiển cho đến khi tay cầm cửa sổ được tạo."

Source= "System.Windows.Forms"

StackTrace:
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.Invoke(Delegate method)
at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) 
in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16

Điều gì đang xảy ra và làm thế nào để tôi sửa chữa nó? Tôi biết rất nhiều vì nó không phải là vấn đề của việc tạo biểu mẫu, vì đôi khi nó sẽ hoạt động một lần và thất bại vào lần tiếp theo, vậy vấn đề có thể là gì?

Tái bút. Tôi thực sự rất tệ ở WinForms, có ai biết một loạt bài viết giải thích toàn bộ mô hình và cách làm việc với nó không?


1
Có điều gì đó kỳ lạ đang xảy ra với liên kết ... đánh dấu và xem trước là chính xác ... kỳ lạ.
George Mauer

Show được gọi trong những ngữ cảnh nào? Nó có bao giờ được gọi từ phương thức khởi tạo của Biểu mẫu không? Có thể hữu ích khi ghi nhật ký các tin nhắn để các cuộc gọi hiển thị so với các tin nhắn được kích hoạt bởi sự kiện HandleCreate để xác minh rằng bạn chỉ đang gọi hiển thị trên các đối tượng đã được tạo các chốt điều khiển của chúng.
Greg D

Ứng dụng để làm gì / nó được thiết kế như thế nào? What does this.Show () làm gì? (Tôi cho rằng nó không chỉ làm được điều này.
Greg D

this.Show () là Form.Show () cơ sở nên bất cứ điều gì làm được. Hộp thoại không bao giờ được đưa lên từ một hàm tạo. Nó được gọi bằng cách triển khai dịch vụ INotifier có phương thức Thông báo (chuỗi) đơn giản
George Mauer

4
Nhìn lại nó, hơn một năm sau, có vẻ như bạn đang gặp lỗi chính xác vì lý do IsHandleCreatedséc tồn tại. Bạn đang cố gắng thay đổi một thuộc tính của (Gửi tin nhắn tới) một điều khiển chưa được tạo. Một điều bạn có thể làm trong tình huống này là xếp hàng đợi các đại diện được gửi trước khi tạo điều khiển, sau đó chạy chúng trong HandleCreatedsự kiện.
Greg D

Câu trả lời:


76

Có thể bạn đang tạo các điều khiển của mình trên chuỗi sai. Xem xét tài liệu sau từ MSDN :

Điều này có nghĩa là InvokeRequired có thể trả về false nếu Invoke không được yêu cầu (cuộc gọi xảy ra trên cùng một luồng) hoặc nếu điều khiển được tạo trên một luồng khác nhưng tay cầm của điều khiển chưa được tạo.

Trong trường hợp xử lý của điều khiển chưa được tạo, bạn không nên chỉ gọi thuộc tính, phương thức hoặc sự kiện trên điều khiển. Điều này có thể khiến tay cầm của điều khiển được tạo trên luồng nền, cô lập điều khiển trên luồng không có máy bơm thông báo và làm cho ứng dụng không ổn định.

Bạn có thể bảo vệ khỏi trường hợp này bằng cách cũng kiểm tra giá trị của IsHandleCreate khi InvokeRequired trả về false trên một chuỗi nền. Nếu tay cầm điều khiển chưa được tạo, bạn phải đợi cho đến khi nó được tạo trước khi gọi Invoke hoặc BeginInvoke. Thông thường, điều này chỉ xảy ra nếu một luồng nền được tạo trong phương thức khởi tạo của biểu mẫu chính cho ứng dụng (như trong Application.Run (MainForm mới ()), trước khi biểu mẫu được hiển thị hoặc Application.Run đã được gọi.

Hãy xem điều này có ý nghĩa gì đối với bạn. (Điều này sẽ dễ lý giải hơn nếu chúng tôi cũng thấy bạn triển khai SafeInvoke)

Giả sử triển khai của bạn giống với triển khai được tham chiếu ngoại trừ kiểm tra đối với IsHandleCreate , hãy làm theo logic:

public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
    if (uiElement == null)
    {
        throw new ArgumentNullException("uiElement");
    }

    if (uiElement.InvokeRequired)
    {
        if (forceSynchronous)
        {
            uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
        else
        {
            uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
    }
    else
    {    
        if (uiElement.IsDisposed)
        {
            throw new ObjectDisposedException("Control is already disposed.");
        }

        updater();
    }
}

Hãy xem xét trường hợp chúng ta đang gọi SafeInvoketừ luồng không phải gui cho một điều khiển có tay cầm chưa được tạo.

uiElementkhông phải là null, vì vậy chúng tôi kiểm tra uiElement.InvokeRequired. Theo tài liệu MSDN (được in đậm) InvokeRequiredsẽ trả về falsevì mặc dù nó được tạo trên một luồng khác, tay cầm vẫn chưa được tạo! Điều này đưa chúng tôi đến elseđiều kiện mà chúng tôi kiểm tra IsDisposedhoặc ngay lập tức tiến hành gọi hành động đã gửi ... từ chuỗi nền !

Tại thời điểm này, tất cả các cược không được kiểm soát bởi vì tay cầm của nó đã được tạo trên một chuỗi không có máy bơm thông báo cho nó, như đã đề cập trong đoạn thứ hai. Có lẽ đây là trường hợp bạn đang gặp phải?


Bạn có nên bao gồm một EndInvokesau BeginInvoke?
Lẻ

@odyodyodys: Câu trả lời ngắn gọn: Không. Đây là một trường hợp kỳ diệu, siêu cụ thể mà bạn không cần phải làm vậy. Câu trả lời dài hơn: Đọc các nhận xét về câu trả lời này: stackoverflow.com/a/714680/6932
Greg D

1
Câu trả lời này và bài viết MSDN đều nói về việc InvokeRequired trả về false vì Handle không được tạo. Nhưng OP nhận được ngoại lệ khi Beginvoke / Invoke được gọi sau khi InvokeRequired trả về true. Làm thế nào InvokeRequired có thể trả về true, khi xử lý chưa được tạo?
thewpfguy

Ngoài ra còn có một điều kiện đua, một điều kiện mà tôi đã chạy, vào wrt IsDisposed. IsDisposed có thể sai khi được kiểm tra nhưng trở thành đúng trước khi hành động đã gửi được thực hiện đầy đủ. Hai lựa chọn dường như là (a) bỏ qua InvalidOperationException và (b) sử dụng khóa để tạo các phần quan trọng từ hành động đã gửi và phương pháp xử lý kiểm soát. Cảm giác đầu tiên giống như một hack và thứ hai là một nỗi đau.
blearyeye

36

Tôi thấy InvokeRequiredkhông đáng tin cậy, vì vậy tôi chỉ đơn giản sử dụng

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}

5
Điều này không thể gây ra có hai xử lý được tạo trên các luồng khác nhau? Tay cầm nên được tạo ra, bạn chỉ cần có để cải thiện thời gian của bạn / thứ tự của các sự kiện ..
Denise Skidmore

Nice - Tôi thích này để chỉ truy cập this.Handle như (a) bạn không có một biến không sử dụng và (b) nó rõ ràng những gì đang xảy ra
Dunc

5
MSDN: "Trong trường hợp xử lý của điều khiển chưa được tạo, bạn không nên chỉ gọi thuộc tính, phương thức hoặc sự kiện trên điều khiển. Điều này có thể khiến xử lý của điều khiển được tạo trên luồng nền, cô lập điều khiển trên một luồng không có máy bơm thông báo và làm cho ứng dụng không ổn định. " Toàn bộ điểm của bài tập là tránh tạo tay cầm trên sợi chỉ sai. Nếu cuộc gọi này xảy ra từ một chuỗi không phải là chuỗi gui, thì bạn đã chết.
Greg D

25

Đây là câu trả lời của tôi cho một câu hỏi tương tự :

Tôi nghĩ (chưa hoàn toàn chắc chắn) rằng điều này là do InvokeRequired sẽ luôn trả về false nếu điều khiển chưa được tải / hiển thị. Tôi đã thực hiện một cách giải quyết có vẻ hiệu quả vào lúc này, đó là tham chiếu đơn giản đến tay cầm của điều khiển được liên kết trong trình tạo của nó, như sau:

var x = this.Handle; 

(Xem http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html )


Bài viết rất thú vị btw. Cảm ơn.
Yann Trevin

Cảm ơn, điều này đã làm việc cho tôi vì tôi có một biểu mẫu ẩn cần được hoạt ảnh trong và ngoài từ một chuỗi nền. Tham khảo Tay cầm là thứ khiến nó hoạt động với tôi
John Mc

Đây vẫn là một vấn đề trong các phiên bản mới nhất của .net, mặc dù nó ít lỗi hơn một "tính năng". Cần lưu ý rằng việc đặt một "đồng hồ" trên đối tượng và duyệt các thuộc tính của nó cũng thực hiện tương tự như nhìn vào tay cầm. Bạn kết thúc với một số gỡ lỗi lượng tử vô nghĩa nơi nó hoạt động khi bạn nhìn vào nó.
Tony Cheetham

5

Phương thức trong bài đăng mà bạn liên kết đến các cuộc gọi Invoke/ BeginInvoketrước khi kiểm tra xem chốt của điều khiển đã được tạo trong trường hợp nó được gọi từ một luồng không tạo điều khiển hay chưa.

Vì vậy, bạn sẽ nhận được ngoại lệ khi phương thức của bạn được gọi từ một luồng khác với luồng đã tạo điều khiển. Điều này có thể xảy ra do xóa các sự kiện hoặc các mục người dùng công việc đã xếp hàng đợi ...

BIÊN TẬP

Nếu bạn kiểm tra InvokeRequiredHandleCreatedtrước khi gọi, bạn sẽ không nhận được ngoại lệ đó.


Nếu tôi hiểu chính xác, bạn đang nói điều này sẽ xảy ra bất cứ khi nào luồng gọi khác với luồng mà điều khiển đã được tạo. Tôi không thể đảm bảo sự kiện sẽ được gọi từ chuỗi nào. Nó có thể là thứ đã tạo ra nó (likelier) là một chuỗi hoàn toàn khác. Làm cách nào để giải quyết vấn đề này?
George Mauer

đúng vậy. Tôi đã chỉnh sửa bài đăng với một điều kiện cần khắc phục sự cố.
Shea

Tôi không tin đây là trường hợp. Tôi đã cập nhật câu hỏi của mình dựa trên nhận xét của bạn, Arnshea.
Greg D

Tôi không hiểu. Tôi cần cửa sổ để hiển thị, tôi không rõ tại sao IsHandleCreated là sai, nhưng không có cửa sổ xuất hiện không phải là một lựa chọn, câu hỏi của tôi là về việc tại sao trên thế giới nó sẽ là sai
George Mauer

Tôi tin rằng IsHandleCreate sẽ trả về false nếu tay cầm bị đóng / điều khiển bị xử lý. Bạn có chắc chắn rằng bạn không bị cắn bởi một lệnh gọi không đồng bộ trên một điều khiển đã từng tồn tại, nhưng không còn nữa không?
Greg D

3

Nếu bạn định sử dụng một Controltừ một luồng khác trước khi hiển thị hoặc làm những việc khác với Control, hãy xem xét việc buộc tạo xử lý của nó trong hàm tạo. Điều này được thực hiện bằng cách sử dụng CreateHandlehàm.

Trong một dự án đa luồng, nơi logic "bộ điều khiển" không có trong WinForm, chức năng này là công cụ trong các Controltrình tạo để tránh lỗi này.


3

Thêm điều này trước khi bạn gọi phương thức gọi:

while (!this.IsHandleCreated) 
   System.Threading.Thread.Sleep(100)

1

Tham chiếu tay cầm của điều khiển được liên kết trong trình tạo của nó, như sau:

Lưu ý : Hãy cảnh giác với giải pháp này, nếu điều khiển có tay cầm thì việc thực hiện những việc như đặt kích thước và vị trí của nó sẽ chậm hơn nhiều. Điều này làm cho InitializeComponent chậm hơn nhiều. Một giải pháp tốt hơn là không làm nền bất cứ điều gì trước khi điều khiển có tay cầm.


0

Tôi đã gặp vấn đề này với loại biểu mẫu đơn giản này:

public partial class MyForm : Form
{
    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
    }

    internal void UpdateLabel(string s)
    {
        Invoke(new Action(() => { label1.Text = s; }));
    }
}

Sau đó, đối với ncác luồng không đồng bộ khác mà tôi đang sử dụng new MyForm().UpdateLabel(text)để thử và gọi luồng giao diện người dùng, nhưng hàm tạo không cung cấp xử lý cho cá thể luồng giao diện người dùng, vì vậy các luồng khác nhận được các xử lý cá thể khác, là Object reference not set to an instance of an objecthoặc Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Để giải quyết vấn đề này, tôi đã sử dụng một đối tượng tĩnh để giữ giao diện người dùng:

public partial class MyForm : Form
{
    private static MyForm _mf;        

    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
        _mf = this;
    }

    internal void UpdateLabel(string s)
    {
        _mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; });
    }
}

Tôi đoán nó đang hoạt động tốt, cho đến nay ...


0
var that = this; // this is a form
(new Thread(()=> {

    var action= new Action(() => {
       something
    }));

    if(!that.IsDisposed)
    {
        if(that.IsHandleCreated)
        {
            //if (that.InvokeRequired)
                that.BeginInvoke(action);
            //else
            // action.Invoke();
        }
        else
            that.HandleCreated+=(sender,event) => {
                action.Invoke();
            };
    }


})).Start();

đây là c # - thiskhông thay đổi dựa trên lệnh gọi, rằng kỹ thuật kiểu javascript sẽ không cần thiết.
George Mauer

chắc chắn đã cố gắng trình bày rõ ràng những gì cần gọi. - gì cũng được
Shimon Doodkin

0

Cái này thì sao :


    public static bool SafeInvoke( this Control control, MethodInvoker method )
    {
        if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated )
        {
            if( control.InvokeRequired )
            {
                control.Invoke( method );
            }
            else
            {
                method();
            }
            return true;
        }
        else return false;
    }
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.