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
나는 같은 것을 궁금해했지만 WPF의 Dispatcher.CheckAccess ()와 관련하여.
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가 제안한 편집 (삽입 점은 첫 번째 코드 스 니펫의 주석 참조) :

// 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에게 Kudos stackoverflow.com/questions/711408/… )
Odys

@ odyodyodys : 좋은 지적입니다. 에 대해 몰랐습니다 ISynchronizeInvoke. 그러나 (Reflector에 따라) 그것에서 파생되는 유일한 유형은입니다 Control. 따라서 adavantage는 제한적입니다.
Olivier Jacot-Descombes

3
@ mike-de-clerk, 귀하의 추가 제안이 걱정 while (!control.Visible) ..sleep..됩니다. 나에게는 코드 지연이 예상되지 않는 호출자 (또는 교착 상태)가있을 수있는 코드에서 잠재적으로 무한한 지연 (어쩌면 무한 루프 일 수도 있음)이므로 나쁜 냄새가납니다. IMHO,의 사용은 Sleep각 발신자의 책임이어야하며, 또는 결과에 대해 명확하게 표시된 별도의 포장지에 있어야합니다. IMHO는 일반적으로 "실패"(예외, 테스트 중 포착) 또는 제어가 준비되지 않은 경우 "아무것도"하지 않는 것이 좋습니다. 코멘트?
ToolmakerSteve

1
@ OlivierJacot-Descombes, thread.invokerequired가 어떻게 작동하는지 설명해 주시면 좋을까요?
Sudhir.net

1
InvokeRequired호출하는 스레드가 컨트롤을 만든 스레드와 다른지 여부를 알려줍니다. Invoke호출 스레드에서 조치가 실행되는 제어 스레드로 조치를 전달합니다. 따라서 예를 들어 click 이벤트 핸들러가 중단되지 않습니다.
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)가 빌드 한대로 수정했습니다.
Oliver

5
완전성을 위해 : WPF에는 다른 디스패치 메커니즘이 있지만 다소 유사합니다. 이 확장 메소드를 여기에서 사용할 수 있습니다. public static void InvokeIfRequired <T> (this T aTarget, Action <T> aActionToExecute) 여기서 T : DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.

1
Lee의 솔루션을 약간 단순화하는 답변을 추가했습니다.
Olivier Jacot-Descombes

안녕하세요, 비슷한 것을 사용하는 곳 에서이 일반적인 구현에서 큰 문제가 발생할 수 있습니다. 컨트롤이 Dispose / Disposed 인 경우 ObjectDisposedException이 발생합니다.
Offler

1
@ Offler-다른 스레드에 배치되는 경우 동기화 문제가 있지만이 방법에서는 문제가되지 않습니다.
Lee

33

다음은 모든 코드에서 사용한 양식입니다.

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

나는 블로그 항목 here을 기반으로했습니다 . 나는이 접근법이 실패하지 않았으므로 InvokeRequired속성을 확인하여 코드를 복잡하게 만들 이유가 없습니다 .

도움이 되었기를 바랍니다.


+1-나는 당신이했던 것과 같은 블로그 글을 우연히 발견했으며 이것이 제안 된 것 중 가장 깨끗한 접근법이라고 생각합니다.
Tom Bushell

3
이 방법을 사용하면 성능이 약간 저하되어 여러 번 호출 할 때 쌓일 수 있습니다. stackoverflow.com/a/747218/724944
서핑 시간

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 's의 개선 / 결합 버전입니다.

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

매번 새 인스턴스를 만드는 대신 Delegate 메서드의 단일 인스턴스를 사용하고 싶습니다. 필자의 경우 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은 여전히 ​​실행중인 배경 스레드가있을 수 있으므로 양식을 닫을 때 True로 설정 한 필드입니다 ...


-3

다음과 같은 코드를 작성해서는 안됩니다.

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

이와 같은 코드가 있으면 응용 프로그램이 스레드로부터 안전하지 않습니다. 이미 다른 스레드에서 DoGUISwitch ()를 호출하는 코드가 있음을 의미합니다. 다른 스레드에 있는지 확인하기에는 너무 늦습니다. DoGUISwitch를 호출하기 전에 InvokeRequire를 호출해야합니다. 다른 스레드에서 메서드 나 속성에 액세스하면 안됩니다.

참조 : Control.InvokeRequired 다음을 읽을 수있는 속성입니다 .

InvokeRequired 속성 외에도 컨트롤에 대한 핸들이 이미 생성 된 경우 호출하기에 안전한 스레드 안전 메서드에는 Invoke, BeginInvoke, EndInvoke 및 CreateGraphics의 네 가지 메서드가 있습니다.

단일 CPU 아키텍처에서는 문제가 없지만 다중 CPU 아키텍처에서는 호출 코드가 실행중인 프로세서에 UI 스레드의 일부가 할당 될 수 있습니다. 프로세서가 UI 스레드와 다른 경우 호출 스레드가 종료되면 Windows는 UI 스레드가 종료되었다고 생각하고 응용 프로그램 프로세스를 종료합니다. 즉 응용 프로그램이 오류없이 종료됩니다.


저기요, 답변 주셔서 감사합니다. 이 질문을 한 지 몇 년이 지났고 (C #을 사용한 지 거의 시간이 걸렸지 만) 조금 더 설명 할 수 있는지 궁금합니다. invoke()컨트롤에 핸들이 부여되기 전에 호출 등 의 특정 위험을 언급하기 위해 연결 한 문서 가 있지만 IMHO는 설명한 내용을 설명하지 않습니다. 이 invoke()넌센스 의 모든 요점은 스레드 안전 방식으로 UI를 업데이트하는 것 입니다. 차단 컨텍스트 에 더 많은 명령을 넣으면 말더듬이 발생할 수 있다고 생각 합니까? (윽 ... 기쁜 나는 M의 $ 기술 사용을 중단 그래서 복잡.!)
톰 Corelis

또한 원래 코드의 사용 빈도 (방법 백)에도 불구하고, 난 당신이 내 듀얼 CPU 바탕 화면에서 설명하는 문제가 관찰되지 않았다 노트에 원하는
톰 Corelis

3
MSDN이 OP가 제공 한 것과 같은 많은 예제를 보여 주므로이 대답이 정확한지 의심 스럽습니다.
공중 무선
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.