การทำให้รูปแบบรหัส InvokeRequired เป็นแบบอัตโนมัติ


179

ฉันได้รับรู้ถึงความเจ็บปวดอย่างถี่ถ้วนว่าบ่อยครั้งที่เราต้องเขียนรูปแบบโค้ดต่อไปนี้ในโค้ด GUI ที่ควบคุมโดยเหตุการณ์

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

กลายเป็น:

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

นี่เป็นรูปแบบที่น่าอึดอัดใจใน C # ทั้งที่ต้องจำและพิมพ์ มีใครมากับทางลัดบางอย่างหรือสร้างสิ่งนี้โดยอัตโนมัติในระดับ? มันจะเจ๋งถ้ามีวิธีแนบฟังก์ชั่นกับวัตถุที่ทำการตรวจสอบนี้โดยไม่ต้องทำงานพิเศษทั้งหมดเช่นobject1.InvokeIfNecessary.visible = trueทางลัดประเภท

คำตอบก่อนหน้านี้ได้กล่าวถึงการทำไม่ได้เพียงแค่เรียก Invoke () ทุกครั้งและแม้กระทั่งไวยากรณ์ Invoke () นั้นไม่มีประสิทธิภาพและยังไม่สะดวกที่จะจัดการ

ดังนั้นมีใครคิดว่าทางลัดใด ๆ ?


2
ฉันสงสัยในสิ่งเดียวกัน แต่เกี่ยวกับ Dispatcher.CheckAccess ของ WPF ของ WPF
Taylor Leese

ฉันคิดถึงคำแนะนำที่บ้าคลั่งที่ได้แรงบันดาลใจมาจากobject1.InvokeIfNecessary.Visible = trueสายงานของคุณ ตรวจสอบคำตอบที่อัปเดตแล้วแจ้งให้เราทราบว่าคุณคิดอย่างไร
Dan Tao

1
เพิ่มตัวอย่างเพื่อช่วยในการใช้วิธีการที่แนะนำโดย Matt Davis: ดูคำตอบของฉัน (ช้า แต่เพิ่งแสดงวิธีสำหรับผู้อ่านในภายหลัง ;-))
Aaron Gage

3
ฉันไม่เข้าใจว่าทำไม Microsoft ไม่ทำอะไรเลยเพื่อลดความซับซ้อนลงใน. NET การสร้างผู้รับมอบสิทธิ์สำหรับการเปลี่ยนแปลงแบบฟอร์มจากเธรดแต่ละครั้งนั้นน่ารำคาญมาก
Kamil

@ Kamil ฉันไม่สามารถตกลงเพิ่มเติม! นี่คือการกำกับดูแลที่ได้รับความแพร่หลาย ภายในเฟรมเวิร์กเพียงจัดการเธรดถ้าจำเป็น ดูเหมือนจะชัดเจน
SteveCinq

คำตอบ:


138

วิธีการของลีสามารถทำให้ง่ายขึ้นอีก

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

และสามารถเรียกได้ว่าเป็นแบบนี้

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

ไม่จำเป็นต้องผ่านการควบคุมเป็นพารามิเตอร์ไปยังผู้รับมอบสิทธิ์ C # จะสร้างปิด


อัปเดต :

ตามที่ผู้โพสต์อื่น ๆControlสามารถทั่วไปเป็น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 ชี้ให้เห็นว่าแตกต่างจากอินเตอร์เฟซที่ต้องใช้อาร์เรย์วัตถุสำหรับวิธีการเป็นรายการพารามิเตอร์สำหรับControlISynchronizeInvokeInvokeaction


อัพเดท 2

การแก้ไขที่แนะนำโดย Mike de Klerk (ดูความคิดเห็นในตัวอย่างรหัสที่ 1 สำหรับจุดแทรก):

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

ดูความคิดเห็นของ ToolmakerSteve ด้านล่างสำหรับข้อกังวลเกี่ยวกับคำแนะนำนี้


2
จะไม่ดีกว่าที่จะมีISynchronizeInvokeแทนControl? (ความรุ่งโรจน์ถึง Jon Skeet stackoverflow.com/questions/711408/… )
Odys

@odyodyodys: จุดดี ISynchronizeInvokeผมไม่ทราบเกี่ยวกับ แต่มีเพียงประเภทเดียวที่เกิดขึ้นจากมัน (อ้างอิงจาก Reflector) Controlดังนั้น adavantage จึงมี จำกัด
Olivier Jacot-Descombes

3
@ while (!control.Visible) ..sleep..ไมค์-de-เสมียนฉันกำลังกังวลเกี่ยวกับข้อเสนอแนะของคุณเพื่อเพิ่ม สำหรับฉันที่มีกลิ่นรหัสที่ไม่ดีเนื่องจากอาจเกิดความล่าช้าที่ไม่ จำกัด (อาจเป็นวงที่ไม่สิ้นสุดในบางกรณี) ในรหัสที่อาจมีผู้โทรที่ไม่คาดว่าจะเกิดความล่าช้า (หรือแม้แต่การหยุดชะงัก) IMHO การใช้งานใด ๆSleepควรเป็นความรับผิดชอบของผู้โทรแต่ละคนหรือควรอยู่ในเสื้อคลุมแยกต่างหากที่มีการระบุไว้อย่างชัดเจนว่าเป็นผลที่ตามมา IMHO โดยปกติจะเป็นการดีกว่าถ้า "ล้มเหลวอย่างหนัก" (ยกเว้นจับระหว่างการทดสอบ) หรือ "ไม่ทำอะไรเลย" หากการควบคุมไม่พร้อม ความคิดเห็น?
ToolmakerSteve

1
@ OlivierJacot-Descombes มันจะดีถ้าคุณโปรดอธิบายว่า thread.invokerequired ทำงานอย่างไร?
Sudhir.net

1
InvokeRequiredบอกว่าเธรดการโทรแตกต่างจากเธรดที่สร้างตัวควบคุมหรือไม่ Invokeส่งการกระทำจากเธรดการโทรไปยังเธรดของตัวควบคุมที่จะดำเนินการ สิ่งนี้ทำให้มั่นใจได้ว่าตัวจัดการเหตุการณ์คลิกจะไม่ถูกขัดจังหวะ
Olivier Jacot-Descombes

133

คุณสามารถเขียนวิธีการขยาย:

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

และใช้มันเช่นนี้

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

แก้ไข: Simpzon ชี้ให้เห็นในความคิดเห็นที่คุณสามารถเปลี่ยนลายเซ็นเป็น:

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

บางทีฉันอาจจะโง่เกินไป แต่รหัสนี้จะไม่รวบรวม ดังนั้นฉันจึงแก้ไขตามที่ฉันสร้างขึ้น (VS2008)
โอลิเวอร์

5
เพื่อความสมบูรณ์: ใน WPF มีกลไกการจัดส่งที่แตกต่างกัน แต่ทำงานคล้ายกัน คุณสามารถใช้วิธีการขยายนี้ที่นั่น: public void InvokeIfRequired <T> (T aTarget, Action <T> aActionToExecute) โดยที่ T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.

1
ฉันเพิ่มคำตอบที่ทำให้โซลูชันของ Lee ง่ายขึ้นเล็กน้อย
Olivier Jacot-Descombes

สวัสดีในฐานะที่ฉันใช้สิ่งที่คล้ายกันอาจมีปัญหาใหญ่เกิดขึ้นจากการใช้งานทั่วไปนี้ ถ้าตัวควบคุมกำลังกำจัด / จำหน่ายคุณจะได้รับ ObjectDisposedException
Offler

1
@Offler - หากพวกเขากำลังถูกกำจัดในเธรดอื่นที่คุณมีปัญหาการซิงโครไนซ์ก็ไม่ใช่ปัญหาในวิธีนี้
ลี

33

นี่คือแบบฟอร์มที่ฉันใช้ในรหัสทั้งหมดของฉัน

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

ฉันได้ตามนี้ในรายการบล็อกที่นี่ ฉันไม่ได้มีวิธีนี้ล้มเหลวฉันดังนั้นฉันจึงเห็นว่าไม่มีเหตุผลใดที่จะทำให้โค้ดของฉันซับซ้อนด้วยการตรวจสอบInvokeRequiredคุณสมบัติ

หวังว่านี่จะช่วยได้


+1 - ฉันสะดุดในรายการบล็อกเดียวกับที่คุณทำและคิดว่านี่เป็นวิธีที่สะอาดที่สุดสำหรับข้อเสนอใด ๆ
Tom Bushell

3
มีประสิทธิภาพเล็กน้อยที่ใช้วิธีการนี้ซึ่งอาจเกิดขึ้นเมื่อเรียกหลายครั้ง stackoverflow.com/a/747218/724944
surfen

4
คุณต้องใช้InvokeRequiredหากสามารถเรียกใช้รหัสก่อนที่จะแสดงการควบคุมหรือคุณจะมีข้อยกเว้นร้ายแรง
56ka

9

สร้างไฟล์ ThreadSafeInvoke.snippet จากนั้นคุณสามารถเลือกคำสั่งการอัพเดทคลิกขวาและเลือก 'Surround With ... ' หรือ 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

นี่คือคำตอบของ Lee's, Oliver's และ 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);
    }
} 

เทมเพลตช่วยให้โค้ดมีความยืดหยุ่นและไม่ต้องใช้การอ่านซึ่งสามารถอ่านได้มากขึ้นในขณะที่ผู้มอบหมายเฉพาะมอบประสิทธิภาพ

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

4

ฉันควรใช้วิธีการหนึ่งอย่างแทนการมอบหมายแทนการสร้างอินสแตนซ์ใหม่ทุกครั้ง ในกรณีของฉันฉันใช้เพื่อแสดงความคืบหน้าและข้อความ (ข้อมูล / ข้อผิดพลาด) จาก Backroundworker คัดลอกและส่งข้อมูลขนาดใหญ่จากอินสแตนซ์ sql ทุกครั้งหลังจากที่ความคืบหน้าประมาณ 70000 และข้อความโทรแบบฟอร์มของฉันหยุดทำงานและแสดงข้อความใหม่ สิ่งนี้ไม่ได้เกิดขึ้นเมื่อฉันเริ่มใช้ตัวแทนผู้ใช้อินสแตนซ์สากลเดียว

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

การใช้งาน:

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

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

    return c.Visible;
});

รหัส:

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

ฉันชอบที่จะทำมันแตกต่างกันเล็กน้อยฉันชอบที่จะเรียกว่า "ตัวเอง" ถ้าจำเป็นกับการกระทำ

    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

นี่เป็นรูปแบบที่มีประโยชน์ IsFormClosing เป็นเขตข้อมูลที่ฉันตั้งค่าเป็นจริงเมื่อฉันปิดแบบฟอร์มเนื่องจากอาจมีเธรดพื้นหลังบางส่วนที่ยังคงทำงานอยู่ ...


-3

คุณไม่ควรเขียนโค้ดที่มีลักษณะดังนี้:

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

หากคุณมีรหัสที่มีลักษณะเช่นนี้แสดงว่าแอปพลิเคชันของคุณไม่ปลอดภัยสำหรับเธรด หมายความว่าคุณมีรหัสที่เรียก DoGUISwitch () จากเธรดอื่นแล้ว สายเกินไปที่จะตรวจสอบเพื่อดูว่ามันอยู่ในเธรดอื่นหรือไม่ InvokeRequire จะต้องถูกเรียกก่อนที่คุณจะโทรไปที่ DoGUISwitch คุณไม่ควรเข้าถึงวิธีหรือคุณสมบัติใด ๆ จากเธรดอื่น

การอ้างอิง: คุณสมบัติ Control.InvokeRequired ซึ่งคุณสามารถอ่านข้อมูลต่อไปนี้:

นอกเหนือจากคุณสมบัติ InvokeRequired มีสี่วิธีในการควบคุมที่เป็นเธรดที่ปลอดภัยในการโทร: เรียกใช้ BeginInvoke, EndInvoke และ CreateGraphics หากสร้างหมายเลขอ้างอิงสำหรับตัวควบคุมแล้ว

ในสถาปัตยกรรม CPU เดียวไม่มีปัญหา แต่ในสถาปัตยกรรม multi-CPU คุณสามารถทำให้ส่วนหนึ่งของเธรด UI ถูกกำหนดให้กับตัวประมวลผลที่เรียกรหัสกำลังเรียกใช้ ... และถ้าตัวประมวลผลนั้นแตกต่างจากที่เธรด UI กำลังทำงานอยู่เมื่อเธรดการโทรจบลง Windows จะคิดว่าเธรด UI ได้สิ้นสุดลงและจะฆ่ากระบวนการแอปพลิเคชันเช่นแอปพลิเคชันของคุณจะออกโดยไม่มีข้อผิดพลาด


เฮ้ขอบคุณสำหรับคำตอบของคุณ เป็นเวลาหลายปีแล้วที่ฉันถามคำถามนี้ (และเกือบจะนานพอที่ฉันทำงานกับ C #) แต่ฉันสงสัยว่าคุณจะอธิบายเพิ่มเติมได้อีกหรือไม่ เอกสารที่คุณเชื่อมโยงเพื่ออ้างถึงอันตรายที่เฉพาะเจาะจงของการโทรinvoke()และคณะก่อนที่การควบคุมจะได้รับการจัดการ แต่ IMHO ไม่ได้อธิบายสิ่งที่คุณอธิบาย ประเด็นทั้งหมดของinvoke()เรื่องไร้สาระทั้งหมดนี้คือการปรับปรุง UI ในลักษณะที่ปลอดภัยต่อเธรดและฉันคิดว่าการใส่คำแนะนำเพิ่มเติมในบริบทการบล็อกจะนำไปสู่การพูดติดอ่าง? (อืม ... ดีใจที่ฉันหยุดใช้เทคโนโลยี M $ ซับซ้อนมาก!)
Tom Corelis

ฉันยังต้องการที่จะทราบว่าแม้จะใช้รหัสดั้งเดิม (ทางกลับเมื่อใด) แต่ฉันไม่ได้สังเกตปัญหาที่คุณอธิบายบนเดสก์ท็อปแบบดูอัล - ซีพียูของฉัน
Tom Corelis

3
ฉันสงสัยว่าคำตอบนี้ถูกต้องเนื่องจาก MSDN แสดงตัวอย่างมากมายเช่นเดียวกับที่ OP ให้
สาธารณะไร้สาย
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.