[C#] InvokeRequired 코드 패턴 자동화

이벤트 중심 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 () 구문도 비효율적이며 여전히 다루기가 어려워졌습니다.

그래서 지름길을 알아 낸 사람이 있습니까?



답변

이의 접근 방식은 더 단순화 될 수있다

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의 의견을 참조하십시오.


답변

확장 방법을 작성할 수 있습니다.

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


답변

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

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

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

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


답변

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>


답변

다음은 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;
});


답변

매번 새 인스턴스를 만드는 대신 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);
}


답변

용법:

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