Tự động hóa mẫu mã InvokeRequired


179

Tôi đã nhận thức sâu sắc về mức độ thường xuyên người ta cần viết mẫu mã sau trong mã GUI hướng sự kiện, trong đó

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

trở thành:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Đây là một mẫu khó xử trong C #, cả để nhớ và gõ. Có ai đã nghĩ ra một số loại phím tắt hoặc cấu trúc tự động hóa mức độ này chưa? Sẽ thật tuyệt nếu có một cách để gắn một chức năng vào các đối tượng thực hiện kiểm tra này mà không phải trải qua tất cả công việc bổ sung này, như một object1.InvokeIfNecessary.visible = truephím tắt loại.

Các câu trả lời trước đây đã thảo luận về tính không thực tế của việc chỉ gọi Invoke () mỗi lần, và thậm chí sau đó cú pháp Invoke () vừa không hiệu quả vừa còn lúng túng để xử lý.

Vì vậy, có ai tìm ra bất kỳ phím tắt?


2
Tôi đã tự hỏi điều tương tự, nhưng liên quan đến Dispatcher của WPF.CheckAccess ().
Taylor Leese

Tôi nghĩ ra một đề nghị khá điên rồ lấy cảm hứng từ object1.InvokeIfNecessary.Visible = truedòng của bạn ; kiểm tra câu trả lời cập nhật của tôi và cho tôi biết những gì bạn nghĩ.
Dan Tao

1
Thêm một đoạn trích để giúp triển khai phương pháp do Matt Davis đề xuất: xem câu trả lời của tôi (muộn nhưng chỉ hiển thị cách thức cho những người đọc sau này ;-))
Aaron Gage

3
Tôi không hiểu tại sao Microsoft không làm gì để đơn giản hóa điều đó trong .NET. Tạo đại biểu cho mỗi thay đổi trên biểu mẫu từ luồng thực sự gây phiền nhiễu.
Kamil

@Kamil Tôi không thể đồng ý nhiều hơn! Đây là một sự giám sát như vậy, do tính phổ biến của nó. Trong khuôn khổ, chỉ cần xử lý luồng nếu cần thiết. Có vẻ rõ ràng.
SteveCinq

Câu trả lời:


138

Cách tiếp cận của Lee có thể được đơn giản hóa hơn nữa

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

Và có thể được gọi như thế này

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

Không cần truyền điều khiển dưới dạng tham số cho đại biểu. C # tự động tạo một bao đóng .


CẬP NHẬT :

Theo một số áp phích khác Controlcó thể được khái quát như ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott chỉ ra rằng không giống như Controlcác ISynchronizeInvokegiao diện đòi hỏi một mảng đối tượng cho Invokephương pháp dạng danh sách tham số cho action.


CẬP NHẬT 2

Các chỉnh sửa được đề xuất bởi Mike de Klerk (xem bình luận trong đoạn mã thứ 1 để biết điểm chèn):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

Xem bình luận của ToolmakerSteve bên dưới để biết mối quan tâm về đề xuất này.


2
Sẽ không tốt hơn để có ISynchronizeInvokethay vì Control? (Kudos to Jon Skeet stackoverflow.com/questions/711408/
mẹo

@odyodyodys: Điểm tốt. Tôi không biết về ISynchronizeInvoke. Nhưng loại duy nhất có nguồn gốc từ nó (theo Reflector) là Control, do đó, adavantage bị hạn chế.
Olivier Jacot-Descombes

3
@ mike-de-clerk, tôi lo ngại về đề xuất của bạn để thêm while (!control.Visible) ..sleep... Đối với tôi có mùi mã xấu, vì nó là một độ trễ có khả năng không bị ràng buộc (thậm chí có thể là một vòng lặp vô hạn trong một số trường hợp), trong mã có thể có những người gọi không mong đợi sự chậm trễ như vậy (hoặc thậm chí là bế tắc). IMHO, bất kỳ việc sử dụng nào Sleepphải là trách nhiệm của mỗi người gọi, HOẶC phải ở trong một trình bao bọc riêng biệt được đánh dấu rõ ràng về hậu quả của nó. IMHO, thông thường sẽ tốt hơn là "thất bại nặng nề" (ngoại trừ, để bắt trong khi thử nghiệm) hoặc "không làm gì" nếu điều khiển chưa sẵn sàng. Bình luận?
ToolmakerSteve

1
@ OlivierJacot-Descombes, Sẽ thật tuyệt, nếu bạn vui lòng giải thích cách thread.invokerequired hoạt động phía sau?
Sudhir.net

1
InvokeRequiredcho biết luồng xử lý có khác với luồng tạo điều khiển không. Invokechuyển hành động từ luồng gọi đến luồng của điều khiển nơi nó được thực thi. Điều này đảm bảo rằng, ví dụ, trình xử lý sự kiện nhấp không bao giờ bị gián đoạn.
Olivier Jacot-Descombes

133

Bạn có thể viết một phương thức mở rộng:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

Và sử dụng nó như thế này:

object1.InvokeIfRequired(c => { c.Visible = true; });

EDIT: Như Simpzon chỉ ra trong các bình luận, bạn cũng có thể thay đổi chữ ký thành:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

Có lẽ tôi quá ngu ngốc, nhưng mã này sẽ không được biên dịch. Vì vậy, tôi đã sửa nó khi nó được xây dựng bởi tôi (VS2008).
Oliver

5
Chỉ để hoàn thiện: Trong WPF có một cơ chế điều phối khác, nhưng nó hoạt động khá tương tự. Bạn có thể sử dụng phương thức tiện ích mở rộng này tại đó: public static void Invoke IfRequired <T> (this T aTarget, Action <T> aActionToExecute) trong đó T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget) } other {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.

1
Tôi đã thêm một câu trả lời đơn giản hóa giải pháp của Lee một chút.
Olivier Jacot-Descombes

Xin chào, vì tôi sử dụng một cái gì đó tương tự, có thể có một vấn đề lớn đến từ việc triển khai chung này. Nếu Điều khiển bị loại bỏ / Loại bỏ, bạn sẽ nhận được ObjectDisposedException.
Offler

1
@ Offerler - Chà nếu chúng bị xử lý trên một luồng khác, bạn có vấn đề về đồng bộ hóa, thì đó không phải là vấn đề trong phương pháp này.
Lee

33

Đây là hình thức tôi đã sử dụng trong tất cả các mã của mình.

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

Tôi đã dựa trên mục blog này ở đây . Tôi đã không có cách tiếp cận này làm tôi thất bại, vì vậy tôi thấy không có lý do gì để làm phức tạp mã của tôi bằng việc kiểm tra InvokeRequiredtài sản.

Hi vọng điêu nay co ich.


+1 - Tôi đã vấp vào cùng một mục blog bạn đã làm và nghĩ rằng đây là cách tiếp cận sạch nhất trong mọi đề xuất
Tom Bushell

3
Có một hiệu suất nhỏ bằng cách sử dụng phương pháp này, có thể chồng chất khi được gọi nhiều lần. stackoverflow.com/a/747218/724944
lướt

4
Bạn phải sử dụng InvokeRequirednếu mã có thể được thực thi trước khi điều khiển được hiển thị hoặc bạn sẽ có một ngoại lệ nghiêm trọng.
56ka

9

Tạo tệp ThreadSafeInvoke.snippet, sau đó bạn chỉ cần chọn các câu lệnh cập nhật, nhấp chuột phải và chọn 'Surround With ...' hoặc Ctrl-K + S:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>ThreadsafeInvoke</Title>
    <Shortcut></Shortcut>
    <Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
    <SnippetTypes>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Code Language="CSharp">
      <![CDATA[
      Invoke( (MethodInvoker) delegate
      {
          $selected$
      });      
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>

6

Đây là phiên bản cải tiến / kết hợp của câu trả lời của Lee, Oliver và Stephan.

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

Mẫu cho phép mã linh hoạt và không có mã, dễ đọc hơn nhiều trong khi đại biểu chuyên dụng cung cấp hiệu quả.

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});

4

Tôi thà sử dụng một thể hiện duy nhất của một phương thức Đại biểu thay vì tạo một thể hiện mới mỗi lần. Trong trường hợp của tôi, tôi đã sử dụng để hiển thị các thông báo tiến trình và (thông tin / lỗi) từ sao chép Backroundworker và truyền dữ liệu lớn từ một cá thể sql. Mỗi lần sau khoảng 70000 tiến trình và các cuộc gọi tin nhắn, biểu mẫu của tôi ngừng hoạt động và hiển thị các tin nhắn mới. Điều này đã không xảy ra khi tôi bắt đầu sử dụng một đại biểu cá thể toàn cầu duy nhất.

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}

3

Sử dụng:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

Mã số:

using System;
using System.ComponentModel;

namespace Extensions
{
    public static class SynchronizeInvokeExtensions
    {
        public static void InvokeIfRequired<T>(this T obj, Action<T> action)
            where T : ISynchronizeInvoke
        {
            if (obj.InvokeRequired)
            {
                obj.Invoke(action, new object[] { obj });
            }
            else
            {
                action(obj);
            }
        }

        public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
            where TIn : ISynchronizeInvoke
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}

2

Tôi muốn làm điều đó một chút khác biệt, tôi thích gọi "bản thân mình" nếu cần với một Hành động,

    private void AddRowToListView(ScannerRow row, bool suspend)
    {
        if (IsFormClosing)
            return;

        if (this.InvokeRequired)
        {
            var A = new Action(() => AddRowToListView(row, suspend));
            this.Invoke(A);
            return;
        }
         //as of here the Code is thread-safe

Đây là một mẫu tiện dụng, IsFormClose là trường mà tôi đặt thành True khi tôi đóng biểu mẫu của mình vì có thể có một số luồng nền vẫn đang chạy ...


-3

Bạn không bao giờ nên viết mã trông như thế này:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Nếu bạn có mã trông như thế này thì ứng dụng của bạn không an toàn cho chuỗi. Điều đó có nghĩa là bạn có mã đã gọi DoGUISwitch () từ một luồng khác. Đã quá muộn để kiểm tra xem nó có ở một luồng khác không. InvokeRequire phải được gọi TRƯỚC KHI bạn thực hiện cuộc gọi tới DoGUISwitch. Bạn không nên truy cập bất kỳ phương thức hoặc thuộc tính từ một luồng khác.

Tham khảo: Control.InvokeRequired Thuộc tính nơi bạn có thể đọc các mục sau:

Ngoài thuộc tính InvokeRequired, có bốn phương thức trên một điều khiển là luồng an toàn để gọi: Gọi, BeginInvoke, EndInvoke và CreatGraphics nếu trình điều khiển cho điều khiển đã được tạo.

Trong một kiến ​​trúc CPU đơn lẻ, không có vấn đề gì, nhưng trong kiến ​​trúc đa CPU, bạn có thể khiến một phần của luồng UI được gán cho bộ xử lý nơi mã cuộc gọi đang chạy ... và nếu bộ xử lý đó khác với nơi xử lý giao diện người dùng đã chạy sau đó khi luồng gọi kết thúc, Windows sẽ nghĩ rằng luồng UI đã kết thúc và sẽ giết quá trình ứng dụng tức là ứng dụng của bạn sẽ thoát mà không gặp lỗi.


Này, cảm ơn câu trả lời của bạn. Đã nhiều năm kể từ khi tôi hỏi câu hỏi này (và gần như chỉ một thời gian kể từ khi tôi làm việc với C #), nhưng tôi tự hỏi liệu bạn có thể giải thích thêm một chút không? Các tài liệu bạn liên kết để đề cập đến một mối nguy hiểm cụ thể của việc gọi invoke()et al trước khi quyền kiểm soát được đưa ra, nhưng IMHO không mô tả những gì bạn đã mô tả. Toàn bộ quan điểm của tất cả những điều invoke()vô nghĩa này là cập nhật giao diện người dùng theo cách an toàn cho chuỗi và tôi có nghĩ rằng việc đặt thêm hướng dẫn trong ngữ cảnh chặn sẽ dẫn đến nói lắp? (Ugh ... rất vui vì tôi đã ngừng sử dụng M $ tech. Quá phức tạp!)
Tom Corelis

Tôi cũng muốn lưu ý rằng mặc dù thường xuyên sử dụng mã gốc (cách trở lại khi nào), tôi đã không quan sát vấn đề bạn mô tả trên máy tính để bàn CPU kép của mình
Tom Corelis

3
Tôi nghi ngờ câu trả lời này là chính xác vì MSDN cho thấy rất nhiều ví dụ giống như OP đã đưa ra.
không dây công cộng
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.