다른 스레드에서 GUI를 어떻게 업데이트합니까?


1392

Label다른 것을 업데이트하는 가장 간단한 방법은 Thread무엇입니까?

  • Form실행 중이며 thread1다른 스레드 ( thread2)를 시작하고 있습니다 .

  • 하지만 thread2일부 파일을 처리하는 나는 업데이 트하려는 Label온을 Form의 현재 상태와 thread2의 작업.

내가 어떻게 할 수 있습니까?


25
.net 2.0 이상에는 이것을위한 BackgroundWorker 클래스가 없습니다. UI 스레드를 인식합니다. 1. BackgroundWorker 만들기 2. 두 명의 델리게이트 추가 (하나는 처리, 다른 하나는 완료)
Preet Sangha

13

4
.NET 4.5 및 C # 5.0에 대한 답변 참조 : stackoverflow.com/a/18033198/2042090
Ryszard Dżegan

5
이 질문은 Gtk # GUI에는 적용되지 않습니다. Gtk #에 대해서는 this and this answer를 참조하십시오 .
hlovdal

주의 :이 질문에 대한 답변은 이제 OT ( "WPF 앱에서 수행 한 작업")와 역사적인 .NET 2.0 아티팩트의 혼란입니다.
Marc L.

답변:


768

.NET 2.0의 경우 다음과 같이 작성한 코드가 있습니다. 원하는대로 정확하게 수행하고의 모든 속성에서 작동합니다 Control.

private delegate void SetControlPropertyThreadSafeDelegate(
    Control control, 
    string propertyName, 
    object propertyValue);

public static void SetControlPropertyThreadSafe(
    Control control, 
    string propertyName, 
    object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
        propertyName, 
        BindingFlags.SetProperty, 
        null, 
        control, 
        new object[] { propertyValue });
  }
}

다음과 같이 호출하십시오.

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

.NET 3.0 이상을 사용하는 경우 위의 메소드를 Control클래스 의 확장 메소드로 다시 작성하면 다음에 대한 호출이 간단 해집니다.

myLabel.SetPropertyThreadSafe("Text", status);

2010 년 10 월 5 일 업데이트 :

.NET 3.0의 경우 다음 코드를 사용해야합니다.

private delegate void SetPropertyThreadSafeDelegate<TResult>(
    Control @this, 
    Expression<Func<TResult>> property, 
    TResult value);

public static void SetPropertyThreadSafe<TResult>(
    this Control @this, 
    Expression<Func<TResult>> property, 
    TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
      as PropertyInfo;

  if (propertyInfo == null ||
      !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
          propertyInfo.Name, 
          propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
      @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
      (SetPropertyThreadSafe), 
      new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
          propertyInfo.Name, 
          BindingFlags.SetProperty, 
          null, 
          @this, 
          new object[] { value });
  }
}

LINQ 및 람다 식을 사용하여 훨씬 깨끗하고 간단하며 안전한 구문을 허용합니다.

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

컴파일시 속성 이름이 검사 될뿐만 아니라 속성 유형도 검사되므로 문자열 값을 부울 속성에 할당 할 수 없으므로 런타임 예외가 발생합니다.

불행히도 이것은 다른 사람 Control의 속성과 가치 를 전달하는 것과 같은 어리석은 일을하는 것을 막지 않으므로 다음은 행복하게 컴파일됩니다.

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

따라서 전달 된 속성이 실제로 Control메서드가 호출되는 속성에 속하는지 확인하기 위해 런타임 검사를 추가 했습니다. 완벽하지는 않지만 .NET 2.0 버전보다 훨씬 우수합니다.

컴파일 타임 안전을 위해이 코드를 개선하는 방법에 대한 추가 제안 사항이 있으면 의견을 말하십시오!


3
this.GetType ()이 propertyInfo.ReflectedType과 같은 것으로 평가되는 경우가 있습니다 (예 : WinForms의 LinkLabel). 나는 C # 경험이 많지 않지만 예외 조건은 다음과 같아야한다고 생각합니다. )! = propertyInfo.ReflectedType) || @ this.GetType (). GetProperty (propertyInfo.Name, propertyInfo.PropertyType) == null)
Corvin

9
@lan SetControlPropertyThreadSafe(myLabel, "Text", status)다른 모듈이나 클래스 또는 폼에서 호출 할 수 있습니다
Smith

71
제공된 솔루션은 불필요하게 복잡합니다. 단순성을 소중히 생각하는 경우 Marc Gravell의 솔루션 또는 Zaid Masud의 솔루션을 참조하십시오.
Frank Hileman

8
호출 할 때마다 많은 리소스가 소비되므로 여러 속성을 업데이트하면이 솔루션은 많은 리소스를 낭비합니다. 이것이 이것이 스레드 안전의 기능이 어쨌든 의도 된 것이라고 생각하지 않습니다. UI 업데이트 작업을 캡슐화하고 속성별로 호출하지 말고 ONCE를 호출하십시오.
콘솔

4
지구상에서 왜이 코드를 BackgroundWorker 구성 요소에 사용합니까?
Andy

1079

가장 간단한 방법은 익명의 방법으로 전달된다 Label.Invoke:

// Running on the worker thread
string newText = "abc";
form.Label.Invoke((MethodInvoker)delegate {
    // Running on the UI thread
    form.Label.Text = newText;
});
// Back on the worker thread

Invoke완료 될 때까지 실행 을 차단합니다. 이것은 동기 코드입니다. 이 질문은 비동기 코드에 대해서는 묻지 않지만 스택 오버플 로에는 배우고 싶을 때 비동기 코드 작성에 대한 많은 내용이 있습니다.


8
OP가 양식을 제외한 클래스 / 인스턴스에 대해 언급하지 않은 것으로
보이며

39
"this"키워드가 "Control"클래스를 참조한다는 것을 잊지 마십시오.
AZ.

8
@codecompleting 그것은 어느 쪽이든 안전하며, 우리는 이미 우리가 노동자임을 알고 있습니다. 왜 우리가 아는 것을 확인합니까?
Marc Gravell

4
@Dragouf는 실제로는 아닙니다-이 방법을 사용하는 요점 중 하나는 작업자에서 실행되는 부분과 UI 스레드에서 실행되는 부분을 이미 알고 있다는 것입니다. 확인할 필요가 없습니다.
Marc Gravell

3
@ Joan.bdm 내가 그것에 대해 언급 할만한 충분한 문맥이 어디에도 없습니다
Marc Gravell

400

긴 작업 처리

.NET 4.5 및 C # 5.0 부터 모든 영역 (GUI 포함) 에서 async - await 키워드 와 함께 태스크 기반 비동기 패턴 (TAP) 을 사용해야합니다 .

TAP는 새로운 개발에 권장되는 비동기 설계 패턴입니다.

대신 비동기 모델 (APM) 프로그래밍이벤트 기반 비동기 패턴 (EAP)을 (후자가 포함되어 있는 BackgroundWorker 클래스 ).

그런 다음 새로운 개발에 권장되는 솔루션은 다음과 같습니다.

  1. 이벤트 핸들러의 비동기 구현 (예, 그게 다) :

    private async void Button_Clicked(object sender, EventArgs e)
    {
        var progress = new Progress<string>(s => label.Text = s);
        await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress),
                                    TaskCreationOptions.LongRunning);
        label.Text = "completed";
    }
  2. UI 스레드에 알리는 두 번째 스레드 구현 :

    class SecondThreadConcern
    {
        public static void LongWork(IProgress<string> progress)
        {
            // Perform a long running work...
            for (var i = 0; i < 10; i++)
            {
                Task.Delay(500).Wait();
                progress.Report(i.ToString());
            }
        }
    }

다음을 주목하십시오 :

  1. 콜백 및 명시 적 스레드없이 순차적으로 작성된 짧고 깨끗한 코드입니다.
  2. Thread 대신 Task .
  3. async 키워드를 사용하면 await 를 사용 하여 이벤트 처리기가 완료 될 때까지 이벤트 핸들러가 완료 상태에 도달하지 못하게하고 그 동안 UI 스레드를 차단하지 않습니다.
  4. SoC (Separation of Concerns) 설계 원칙을 지원하고 명시적인 디스패처 및 호출이 필요하지 않은 Progress 클래스 ( IProgress 인터페이스 참조 ) 생성 위치 (여기서는 UI 스레드)에서 현재 SynchronizationContext 를 사용합니다 .
  5. TaskCreationOptions.LongRunning 작업을 ThreadPool에 대기시키지 않습니다 .

더 많은 예제 자세한 A를 참조하십시오 : C 번호의 미래 : 좋은 일이있는 사람 'await를'에게 오는 에 의해 조셉 알바 하리 .

UI 스레딩 모델 개념에 대해서도 참조하십시오 .

예외 처리

아래 스 니펫은 Enabled백그라운드를 실행하는 동안 여러 번의 클릭을 방지 하기 위해 예외를 처리하고 버튼의 속성을 전환하는 방법의 예입니다 .

private async void Button_Click(object sender, EventArgs e)
{
    button.Enabled = false;

    try
    {
        var progress = new Progress<string>(s => button.Text = s);
        await Task.Run(() => SecondThreadConcern.FailingWork(progress));
        button.Text = "Completed";
    }
    catch(Exception exception)
    {
        button.Text = "Failed: " + exception.Message;
    }

    button.Enabled = true;
}

class SecondThreadConcern
{
    public static void FailingWork(IProgress<string> progress)
    {
        progress.Report("I will fail in...");
        Task.Delay(500).Wait();

        for (var i = 0; i < 3; i++)
        {
            progress.Report((3 - i).ToString());
            Task.Delay(500).Wait();
        }

        throw new Exception("Oops...");
    }
}

2
경우 SecondThreadConcern.LongWork()예외를 throw, 그것은 UI 스레드에 의해 체포 될 수 있는가? 이것은 훌륭한 게시물입니다, btw.
kdbanman

2
귀하의 요구 사항을 충족시키기 위해 답변에 추가 섹션을 추가했습니다. 문안 인사.
Ryszard Dżegan

3
ExceptionDispatchInfo 클래스는 비동기 await를 패턴으로 UI 스레드에서 배경 예외를 rethrowing의 기적에 대한 책임이 있습니다.
Ryszard Dżegan

1
이 작업을 수행하는 방법이 Invoke / Begin을 호출하는 것보다 더 장황하다고 생각합니까?!
MeTitus

2
Task.Delay(500).Wait()? 현재 스레드를 차단하기 위해 작업을 만드는 요점은 무엇입니까? 스레드 풀 스레드를 차단해서는 안됩니다!
Yarik

236

Marc Gravell의 .NET 4를위한 가장 간단한 솔루션 변형 :

control.Invoke((MethodInvoker) (() => control.Text = "new text"));

또는 대신 Action 대리자를 사용하십시오.

control.Invoke(new Action(() => control.Text = "new text"));

이 두 가지를 비교하려면 여기를 참조하십시오. MethodInvoker 대 Control.BeginInvoke


1
이 예에서 '제어'란 무엇입니까? 내 UI 컨트롤? 레이블 컨트롤의 WPF에서이를 구현하려고하면 Invoke가 내 레이블의 구성원이 아닙니다.
Dbloom

@styxriver stackoverflow.com/a/3588137/206730 과 같은 확장 방법 은 무엇입니까 ?
Kiquenet April

'액션 y;'선언 클래스 또는 메소드 내부에서 텍스트 속성을 변경 하고이 코드로 텍스트를 업데이트하십시오. 'yourcontrol.Invoke (y = () => yourcontrol.Text = "new text");'
Antonio Leite

4
@Dbloom WinForms 전용이기 때문에 회원이 아닙니다. WPF의 경우 Dispatcher.Invoke
sLw

1
이 솔루션을 따르고 있지만 때로는 UI가 업데이트되지 않았습니다. this.refresh()도움이된다면 GUI를 무효화하고 다시 페인트 해야한다는 것을 알았 습니다.
Rakibul Haq

137

.NET 3.5 이상에 대한 실행 및 확장 확장 방법

using System;
using System.Windows.Forms;

public static class ControlExtensions
{
    /// <summary>
    /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
    /// </summary>
    /// <param name="control"></param>
    /// <param name="code"></param>
    public static void UIThread(this Control @this, Action code)
    {
        if (@this.InvokeRequired)
        {
            @this.BeginInvoke(code);
        }
        else
        {
            code.Invoke();
        }
    }
}

다음 코드 줄을 사용하여 호출 할 수 있습니다.

this.UIThread(() => this.myLabel.Text = "Text Goes Here");

5
@이 사용법의 요점은 무엇입니까? "제어"가 동일하지 않습니까? @this에 어떤 이점이 있습니까?
아가일

14
@jeromeyers- @this는 단순히 변수 이름이며,이 경우 확장을 호출하는 현재 컨트롤에 대한 참조입니다. 소스로 바꾸거나 보트를 떠 다니는 이름으로 바꿀 수 있습니다. 내가 사용하는 @this정상 (비 확장) 코드에서 '이'키워드를 사용하여 (적어도 내 머리에) 확장을 호출하고 일관성이다 '이 제어'를 참조 있기 때문에.
StyxRiver

1
이것은 훌륭하고 쉽고 나에게 최고의 솔루션입니다. UI 스레드에서해야 할 모든 작업을 포함 할 수 있습니다. 예 : this.UIThread (() => {txtMessage.Text = message; listBox1.Items.Add (message);});
Auto

1
나는이 솔루션을 정말 좋아한다. 마이너 니트 :이 방법을 이름 것 OnUIThread보다는 UIThread.
ToolmakerSteve

2
그렇기 때문에이 확장의 이름을 지정했습니다 RunOnUiThread. 그러나 그것은 개인적인 취향입니다.
Grisgram

66

이것이 당신이 해야하는 고전적인 방법입니다.

using System;
using System.Windows.Forms;
using System.Threading;

namespace Test
{
    public partial class UIThread : Form
    {
        Worker worker;

        Thread workerThread;

        public UIThread()
        {
            InitializeComponent();

            worker = new Worker();
            worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
            workerThread = new Thread(new ThreadStart(worker.StartWork));
            workerThread.Start();
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
        {
            // Cross thread - so you don't get the cross-threading exception
            if (this.InvokeRequired)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    OnWorkerProgressChanged(sender, e);
                });
                return;
            }

            // Change control
            this.label1.Text = e.Progress;
        }
    }

    public class Worker
    {
        public event EventHandler<ProgressChangedArgs> ProgressChanged;

        protected void OnProgressChanged(ProgressChangedArgs e)
        {
            if(ProgressChanged!=null)
            {
                ProgressChanged(this,e);
            }
        }

        public void StartWork()
        {
            Thread.Sleep(100);
            OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
            Thread.Sleep(100);
        }
    }


    public class ProgressChangedArgs : EventArgs
    {
        public string Progress {get;private set;}
        public ProgressChangedArgs(string progress)
        {
            Progress = progress;
        }
    }
}

작업자 스레드에 이벤트가 있습니다. UI 스레드는 다른 스레드를 시작하여 작업을 수행하고 해당 작업자 이벤트를 연결하여 작업자 스레드의 상태를 표시 할 수 있습니다.

그런 다음 UI에서 스레드를 교차하여 레이블이나 진행률 표시 줄과 같은 실제 컨트롤을 변경해야합니다.


62

간단한 해결책은을 사용하는 것 Control.Invoke입니다.

void DoSomething()
{
    if (InvokeRequired) {
        Invoke(new MethodInvoker(updateGUI));
    } else {
        // Do Something
        updateGUI();
    }
}

void updateGUI() {
    // update gui here
}

단순성을 위해 잘했습니다! 간단 할뿐만 아니라 잘 작동합니다! Microsoft가 왜 그렇게 간단하지 않을 수 있었는지 이해하지 못했습니다! 메인 스레드에서 한 줄을 호출하려면 몇 가지 함수를 작성해야합니다!
MBH

1
@MBH 동의합니다. BTW, 위의 stackoverflow.com/a/3588137/199364 답변에서 확장 방법을 정의 했습니까? 사용자 지정 유틸리티 클래스에서 한 번 수행하면 Microsoft가 우리를 위해하지 않은 것에 더 이상 신경 쓰지 않아도됩니다. :)
ToolmakerSteve

@ToolmakerSteve 그것이 정확히 무엇을 의미하는지! 당신은 우리가 방법을 찾을 수 있지만, 나는 일반적인 해결책이있는 DRY (반복하지 마십시오) 관점에서 많은 시간을 절약 할 수있는 Microsoft의 최소한의 노력으로 해결할 수 있습니다. 프로그래머 :)
MBH

47

스레딩 코드는 종종 버그가 있으며 항상 테스트하기가 어렵습니다. 백그라운드 작업에서 사용자 인터페이스를 업데이트하기 위해 스레딩 코드를 작성할 필요가 없습니다. BackgroundWorker 클래스를 사용하여 작업과 ReportProgress 메서드를 실행 하여 사용자 인터페이스를 업데이트하십시오. 일반적으로 완료율 만보고하지만 상태 객체를 포함하는 또 다른 과부하가 있습니다. 다음은 문자열 객체 만보고하는 예입니다.

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "A");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "B");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "C");
    }

    private void backgroundWorker1_ProgressChanged(
        object sender, 
        ProgressChangedEventArgs e)
    {
        label1.Text = e.UserState.ToString();
    }

항상 같은 필드를 업데이트하려면 괜찮습니다. 보다 복잡한 업데이트가 필요한 경우 UI 상태를 나타내는 클래스를 정의하여 ReportProgress 메서드에 전달할 수 있습니다.

마지막으로 WorkerReportsProgress플래그 를 설정해야합니다 . 그렇지 않으면 ReportProgress메서드가 완전히 무시됩니다.


2
처리가 끝나면를 통해 사용자 인터페이스를 업데이트 할 수도 있습니다 backgroundWorker1_RunWorkerCompleted.
DavidRR

41

답변의 대부분은 사용 Control.InvokeA는 어떤 일이 기다리고 경쟁 조건 . 예를 들어, 허용되는 답변을 고려하십시오.

string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate { 
    someLabel.Text = newText; // runs on UI thread
});

사용자가 폼을 닫기 직전에 폼을 닫으면 this.Invoke( 오브젝트 this는 기억하십시오 Form), ObjectDisposedException해고 될 가능성이 있습니다.

해결책은 SynchronizationContext특히 hamilton.danielb이 제안한 SynchronizationContext.Current것처럼 을 사용하는 것입니다 (다른 답변 은 완전히 불필요 한 특정 구현에 의존합니다 ). 비록 일반적으로 작업자 스레드가 기다릴 필요가 없기 때문에 대신 사용하도록 코드를 약간 수정합니다 .SynchronizationContextSynchronizationContext.PostSynchronizationContext.Send

public partial class MyForm : Form
{
    private readonly SynchronizationContext _context;
    public MyForm()
    {
        _context = SynchronizationContext.Current
        ...
    }

    private MethodOnOtherThread()
    {
         ...
         _context.Post(status => someLabel.Text = newText,null);
    }
}

.NET 4.0 이상에서는 실제로 비동기 작업을위한 작업을 사용해야합니다. 동등한 작업 기반 접근법에 대한 n-san의 답변을 참조하십시오 (TaskScheduler.FromCurrentSynchronizationContext )에 .

마지막으로 .NET 4.5 이상 에서는 Ryszard Dżegan이 시연 한 것처럼 장기 실행 작업이 여전히 작동하는 동안 UI 코드를 실행해야하는 경우에 사용할 수 있습니다 Progress<T>(기본적으로 SynchronizationContext.Current생성시 캡처 ) .


37

올바른 스레드에서 업데이트가 발생하는지 확인해야합니다. UI 스레드

이렇게하려면 이벤트 핸들러를 직접 호출하는 대신 이벤트 핸들러를 호출해야합니다.

다음과 같이 이벤트를 제기하면됩니다.

(코드는 여기 머리에 입력되었으므로 올바른 구문 등을 확인하지는 않았지만 진행해야합니다.)

if( MyEvent != null )
{
   Delegate[] eventHandlers = MyEvent.GetInvocationList();

   foreach( Delegate d in eventHandlers )
   {
      // Check whether the target of the delegate implements 
      // ISynchronizeInvoke (Winforms controls do), and see
      // if a context-switch is required.
      ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;

      if( target != null && target.InvokeRequired )
      {
         target.Invoke (d, ... );
      }
      else
      {
          d.DynamicInvoke ( ... );
      }
   }
}

WPF 컨트롤은 ISynchronizeInvoke인터페이스를 구현하지 않기 때문에 위 코드는 WPF 프로젝트에서 작동하지 않습니다 .

윈도우 양식 및 WPF, 그리고 다른 모든 플랫폼에서 작동 위의 코드, 당신은 한 번 봐 가질 수 있는지 확인하기 위해 AsyncOperation, AsyncOperationManager그리고SynchronizationContext 클래스를.

이런 식으로 이벤트를 쉽게 발생시키기 위해 확장 메서드를 만들었습니다. 다음과 같이 호출하면 이벤트 발생을 단순화 할 수 있습니다.

MyEvent.Raise(this, EventArgs.Empty);

물론 BackGroundWorker 클래스를 사용하여이 문제를 추상화 할 수도 있습니다.


실제로, 나는이 문제로 내 GUI 코드를 '혼잡'하고 싶지 않습니다. 내 GUI는 호출 해야하는지 여부를 신경 쓰지 않아야합니다. 다시 말해, 나는 context-swithc를 수행하는 것이 GUI의 책임이라고 생각하지 않습니다.
Frederik Gheysels

1
델리게이트를 분리하는 등의 작업은 과도하게 수행됩니다. SynchronizationContext.Current.Send (delegate {MyEvent (...);}, null);
Marc Gravell

항상 SynchronizationContext에 액세스 할 수 있습니까? 클래스가 클래스 lib에 있더라도?
Frederik Gheysels

29

GUI 스레드에서 메소드를 호출해야합니다. Control.Invoke를 호출하여이를 수행 할 수 있습니다.

예를 들면 다음과 같습니다.

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}

1
호출 줄에서 컴파일러 오류가 발생합니다. 'System.Windows.Forms.Control.Invoke (System.Delegate, object [])'에 대해 가장 오버로드 된 메소드 일치에는 잘못된 인수가 있습니다.
CruelIO

28

사소한 시나리오로 인해 실제로 상태에 대한 UI 스레드 폴링이 있습니다. 나는 그것이 매우 우아 할 수 있다고 생각합니다.

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

이 방법은 ISynchronizeInvoke.Invokeand ISynchronizeInvoke.BeginInvoke메소드를 사용할 때 필요한 마샬링 작업을 피합니다 . 마샬링 기술을 사용하는 데 아무런 문제가 없지만 알아야 할 몇 가지주의 사항이 있습니다.

  • BeginInvoke너무 자주 전화하지 않으면 메시지 펌프가 오버런 될 수 있습니다.
  • 호출 Invoke작업자 스레드에서하는 것은 차단 호출입니다. 해당 스레드에서 수행중인 작업이 일시적으로 중단됩니다.

이 답변에서 제안하는 전략은 스레드의 통신 역할을 반대로합니다. 작업자 스레드가 데이터를 푸시하는 대신 UI 스레드가 폴링합니다. 이것은 많은 시나리오에서 사용되는 일반적인 패턴입니다. 작업자 스레드에서 진행 정보를 표시하기 만하면이 솔루션이 마샬링 솔루션의 훌륭한 대안이라는 것을 알 수 있습니다. 다음과 같은 장점이 있습니다.

  • UI와 작업자 스레드는 이들을 밀접하게 연결하는 접근 방식 Control.Invoke이나 Control.BeginInvoke접근 방식 과 달리 느슨하게 결합 된 상태로 유지 됩니다.
  • UI 스레드는 작업자 스레드의 진행을 방해하지 않습니다.
  • 작업자 스레드는 UI 스레드가 업데이트에 소비하는 시간을 지배 할 수 없습니다.
  • UI 및 작업자 스레드가 작업을 수행하는 간격은 독립적으로 유지 될 수 있습니다.
  • 작업자 스레드가 UI 스레드의 메시지 펌프를 오버런 할 수 없습니다.
  • UI 스레드는 UI가 업데이트되는시기와 빈도를 알려줍니다.

3
좋은 생각. 언급하지 않은 유일한 것은 WorkerThread가 완료되면 타이머를 올바르게 처리하는 방법입니다. 응용 프로그램이 종료 될 때 (예 : 사용자가 응용 프로그램을 닫을 때) 문제가 발생할 수 있습니다. 이 문제를 해결하는 방법이 있습니까?
Matt

@Matt Elapsed이벤트에 익명 처리기를 사용하는 대신 멤버 메서드를 사용하여 양식을 배치 할 때 타이머를 제거 할 수 있습니다.
Phil1970

@ Phil1970-좋은 지적입니다. 나중에 처분 ​​컨텍스트에서 내가 옳고 그름을 좋아 System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };하고 그것을 할당하는 것을 의미 했습니까? 그리고 여기에 논의 된 조언을 따르는 처분 / 폐쇄를 위해 . m_Timer.Elapsed += handler;m_Timer.Elapsed -= handler;
Matt

27

이전 답변의 호출 항목은 필요하지 않습니다.

WindowsFormsSynchronizationContext를 확인해야합니다.

// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();

...

// In some non-UI Thread

// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);

...

void UpdateGUI(object userData)
{
    // Update your GUI controls here
}

4
Post 메소드가 후드에서 무엇을 사용한다고 생각하십니까? :)
increddibelly

23

이것은 .NET Framework 3.0을 사용하는 위의 솔루션과 유사하지만 컴파일 타임 안전 지원 문제를 해결했습니다 .

public  static class ControlExtension
{
    delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);

    public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
    {
        if (source.InvokeRequired)
        {
            var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
            source.Invoke(del, new object[]{ source, selector, value});
        }
        else
        {
            var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
            propInfo.SetValue(source, value, null);
        }
    }
}

쓰다:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

사용자가 잘못된 데이터 유형을 전달하면 컴파일러가 실패합니다.

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");

23

살 베테! 이 질문을 검색 한 결과 FrankGOregon Ghost 의 답변이 가장 유용한 것으로 나타났습니다 . 이제 Visual Basic으로 코드를 작성하고 변환기를 통해이 스 니펫을 실행했습니다. 그래서 그것이 어떻게 나오는지 잘 모르겠습니다.

나는 일종의 로깅 디스플레이로 사용되는 form_Diagnostics,리치 텍스트 상자 가있는 대화 상자 양식 을 가지고 updateDiagWindow,있습니다. 모든 스레드에서 텍스트를 업데이트 할 수 있어야했습니다. 추가 줄을 사용하면 창을 자동으로 최신 줄로 스크롤 할 수 있습니다.

그래서 이제 스레딩없이 작동한다고 생각하는 방식으로 전체 프로그램의 어느 곳에서나 한 줄로 디스플레이를 업데이트 할 수 있습니다.

  form_Diagnostics.updateDiagWindow(whatmessage);

메인 코드 (이것은 폼의 클래스 코드 안에 넣습니다) :

#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion

21

많은 목적을 위해 다음과 같이 간단합니다.

public delegate void serviceGUIDelegate();
private void updateGUI()
{
  this.Invoke(new serviceGUIDelegate(serviceGUI));
}

"serviceGUI ()"는 원하는만큼 많은 컨트롤을 변경할 수있는 양식 (this) 내의 GUI 레벨 메소드입니다. 다른 스레드에서 "updateGUI ()"를 호출하십시오. 매개 변수를 추가하여 값을 전달하거나, 스레드 사이에 충돌이 발생하여 불안정성을 유발할 수있는 경우 필요한 경우 클래스 범위 변수를 잠금과 함께 사용할 수 있습니다 (아마도 더 빠름). 비 GUI 스레드가 시간이 중요한 경우 (Brian Gideon의 경고를 염두에두고) Invoke 대신 BeginInvoke를 사용하십시오.


21

이것은 Ian Kemp 솔루션의 C # 3.0 변형에서 다음과 같습니다.

public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control
{
    var memberExpression = property.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    if (control.InvokeRequired)
        control.Invoke(
            (Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread,
            new object[] { control, property, value }
        );
    else
        propertyInfo.SetValue(control, value, null);
}

당신은 이것을 다음과 같이 부릅니다.

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. "as MemberExpression"의 결과에 널 검사를 추가합니다.
  2. 정적 타입 안전성을 향상시킵니다.

그렇지 않으면 원본이 매우 좋은 솔루션입니다.


21
Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

교착 상태가 발생할 가능성이 적으므로 BeginInvoke()선호 되는 것이 좋습니다 Invoke()(단, 텍스트를 레이블에 지정할 때 문제가되지는 않습니다).

사용할 때 Invoke()메소드가 리턴되기를 기다리고 있습니다. 이제 호출 된 코드에서 스레드를 기다려야 할 무언가를 수행 할 수 있습니다. 스레드를 호출 해야하는 일부 함수에 묻혀 있으면 이벤트 처리기를 통해 간접적으로 발생할 수 있습니다. 그래서 당신은 스레드를 기다리고있을 것이고, 스레드는 당신을 기다리고있을 것이고 당신은 교착 상태입니다.

이로 인해 실제로 출시 된 일부 소프트웨어가 중단되었습니다. 로 교체 Invoke()하여 쉽게 해결할 수있었습니다 BeginInvoke(). 리턴 값이 필요한 경우 동기 조작이 필요한 경우가 아니면를 사용하십시오 BeginInvoke().


20

같은 문제가 발생했을 때 Google의 도움을 구했지만 간단한 해결책을 제시하는 대신 예제 MethodInvoker와 blah blah blah를 제공하여 더 혼란 스럽습니다 . 그래서 나는 그것을 스스로 해결하기로 결정했습니다. 내 해결책은 다음과 같습니다.

다음과 같이 대리인을 만드십시오.

Public delegate void LabelDelegate(string s);

void Updatelabel(string text)
{
   if (label.InvokeRequired)
   {
       LabelDelegate LDEL = new LabelDelegate(Updatelabel);
       label.Invoke(LDEL, text);
   }
   else
       label.Text = text
}

이 기능을 다음과 같은 새 스레드에서 호출 할 수 있습니다

Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();

와 혼동하지 마십시오 Thread(() => .....). 스레드에서 작업 할 때 익명 함수 또는 람다 식을 사용합니다. 코드 줄을 줄이려면 ThreadStart(..)여기서 설명하지 않아도되는 방법을 사용할 수 있습니다.


17

간단히 다음과 같이 사용하십시오 :

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });

을 가지고 있다면 e.ProgressPercentage, 이것을 호출하는 메소드의 UI 스레드에 있지 않습니까?
LarsTech

ProgressChanged 이벤트는 UI 스레드에서 실행됩니다. 이것이 BackgroundWorker를 사용하는 편의 중 하나입니다. Completed 이벤트도 GUI에서 실행됩니다. 비 UI 스레드에서 실행되는 유일한 것은 DoWork 메소드입니다.
LarsTech

15

기존 대리인을 사용할 수 있습니다 Action.

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}

14

내 버전은 한 줄 의 재귀 "만트라" 를 삽입하는 입니다.

인수가없는 경우 :

    void Aaaaaaa()
    {
        if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra

        // Your code!
    }

인수가있는 함수의 경우 :

    void Bbb(int x, string text)
    {
        if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
        // Your code!
    }

그것은 IT 입니다.


약간의 논증 : 일반적으로 코드 가독성이 {} 뒤에if () 문장에서 문장 . 그러나이 경우에는 일상적으로 동일한 "만트라"입니다. 이 방법이 프로젝트에서 일관된 경우 코드 가독성을 깨뜨리지 않습니다. 또한 코드가 흩어지지 않도록합니다 (5 개 대신 1 줄의 코드).

보시 if(InvokeRequired) {something long}다시피 "이 함수는 다른 스레드에서 호출해도 안전합니다."


13

이것을 사용하여 레이블을 새로 고치십시오.

public static class ExtensionMethods
{
    private static Action EmptyDelegate = delegate() { };

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}

그것은을위한 Windows Forms의 ?
Kiquenet

13

클래스 변수를 작성하십시오.

SynchronizationContext _context;

UI를 생성하는 생성자에서 설정하십시오.

var _context = SynchronizationContext.Current;

라벨을 업데이트하려는 경우 :

_context.Send(status =>{
    // UPDATE LABEL
}, null);

12

호출 및 위임을 사용해야합니다

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });

12

다른 답변의 대부분은이 질문에 대해 약간 복잡합니다 (C #을 처음 사용합니다).

나는이 WPF의 응용 프로그램을 아래로 작업자를 정의했습니다 :

발행물:

BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
    // This is my DoWork function.
    // It is given as an anonymous function, instead of a separate DoWork function

    // I need to update a message to textbox (txtLog) from this thread function

    // Want to write below line, to update UI
    txt.Text = "my message"

    // But it fails with:
    //  'System.InvalidOperationException':
    //  "The calling thread cannot access this object because a different thread owns it"
}

해결책:

workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
    // The below single line works
    txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}

위의 줄이 무엇을 의미하는지 아직 알지 못하지만 작동합니다.

대한 윈폼 :

해결책:

txtLog.Invoke((MethodInvoker)delegate
{
    txtLog.Text = "my message";
});

문제는 WPF가 아닌 Winforms에 관한 것입니다.
Marc L.

감사. 위의 WinForms 솔루션을 추가했습니다.
Manohar Reddy Poreddy

...이 같은 질문에 대한 다른 많은 답변의 사본 일뿐입니다. 왜 솔루션에 참여하지 않고 답을 삭제 하시겠습니까?
마크 L.

흠, 당신은, 만약 당신이주의를 기울여 내 대답을 읽고, 시작 부분 (답을 쓴 이유)을 읽고, 조금 더주의를 기울이면 오늘 같은 문제를 겪고 공감하는 사람이 있다는 것을 알 수 있습니다. 내 간단한 대답, 그리고이 모든 일이 왜 일어 났는지에 대한 실제 이야기를 예견 할 수 있다면 더 많은 attn으로, 구글은 wpf를 검색 할 때조차도 나를 여기로 보냅니다. 당신이 이러한 명백한 3 가지 이유를 놓친 이후로, 나는 왜 당신이 당신의 공감대를 제거하지 않을지를 이해할 수 있습니다. 괜찮은 것을 청소하는 대신 훨씬 어려운 새로운 것을 만드십시오.
Manohar Reddy Poreddy


8

예를 들어, 현재 스레드가 아닌 다른 컨트롤에 액세스하십시오.

Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
    lblThreshold.Text = Speed_Threshold.ToString();
}));

lblThreshold레이블이며, Speed_Threshold전역 변수입니다.


8

UI 스레드에있을 때 동기화 컨텍스트 작업 스케줄러를 요청할 수 있습니다. UI 스레드의 모든 것을 예약 하는 TaskScheduler 를 제공합니다 .

그런 다음 결과가 준비되면 UI 스레드에서 예약 된 다른 작업이이를 선택하여 레이블에 할당하도록 작업을 연결할 수 있습니다.

public partial class MyForm : Form
{
  private readonly TaskScheduler _uiTaskScheduler;
  public MyForm()
  {
    InitializeComponent();
    _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  }

  private void buttonRunAsyncOperation_Click(object sender, EventArgs e)
  {
    RunAsyncOperation();
  }

  private void RunAsyncOperation()
  {
    var task = new Task<string>(LengthyComputation);
    task.ContinueWith(antecedent =>
                         UpdateResultLabel(antecedent.Result), _uiTaskScheduler);
    task.Start();
  }

  private string LengthyComputation()
  {
    Thread.Sleep(3000);
    return "47";
  }

  private void UpdateResultLabel(string text)
  {
    labelResult.Text = text;
  }
}

이것은 현재 동시 코드를 작성 하는 선호되는 방법 인 작업 (스레드가 아닌)에서 작동합니다 .


1
호출은 Task.Start일반적으로 좋은 연습하지 blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspx
오핫 슈나이더

8

방금 답변을 읽었으며 이것은 매우 인기있는 주제로 보입니다. 현재 .NET 3.5 SP1 및 Windows Forms를 사용하고 있습니다.

InvokeRequired를 사용하는 이전 답변에서 크게 설명한 잘 알려진 수식 속성을 은 대부분의 경우를 다루지 만 전체 풀에는 적용되지 않습니다.

어떤 경우 핸들은 아직 생성되지 않았습니다?

여기에 설명 된 InvokeRequired 속성 (MSDN에 대한 Control.InvokeRequired 속성 참조) 은 GUI 스레드가 아닌 스레드에서 호출 한 경우 true를, GUI 스레드에서 호출 된 경우 또는 Handle 이 아직 생성되지 않았습니다.

다른 스레드에서 모달 양식을 표시하고 업데이트하려는 경우 예외가 발생할 수 있습니다. 해당 양식을 모달로 표시하려면 다음을 수행하십시오.

private MyForm _gui;

public void StartToDoThings()
{
    _gui = new MyForm();
    Thread thread = new Thread(SomeDelegate);
    thread.Start();
    _gui.ShowDialog();
}

또한 대리인은 GUI에서 레이블을 업데이트 할 수 있습니다.

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.InvokeRequired)
        _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
    else
        _gui.Label1.Text = "Done!";
}

이로 인해 GUI 스레드가 Form 's Handle 을 작성하는 데 걸리는 시간보다 레이블 업데이트 이전의 조작이 "시간이 덜 소요"(읽고 단순화로 해석)하는 경우 InvalidOperationException 이 발생할 수 있습니다 . 이것은 ShowDialog () 메서드 내에서 발생합니다 .

또한 다음과 같이 핸들을 확인해야 합니다.

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.IsHandleCreated)  //  <---- ADDED
        if(_gui.InvokeRequired)
            _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
        else
            _gui.Label1.Text = "Done!";
}

핸들 이 아직 작성되지 않은 경우 수행 할 조작을 처리 할 수 ​​있습니다 . 위의 코드에 표시된대로 GUI 업데이트를 무시하거나 대기 (더 위험) 할 수 있습니다. 질문에 대답해야합니다.

선택 사항 : 개인적으로 다음을 코딩했습니다.

public class ThreadSafeGuiCommand
{
  private const int SLEEPING_STEP = 100;
  private readonly int _totalTimeout;
  private int _timeout;

  public ThreadSafeGuiCommand(int totalTimeout)
  {
    _totalTimeout = totalTimeout;
  }

  public void Execute(Form form, Action guiCommand)
  {
    _timeout = _totalTimeout;
    while (!form.IsHandleCreated)
    {
      if (_timeout <= 0) return;

      Thread.Sleep(SLEEPING_STEP);
      _timeout -= SLEEPING_STEP;
    }

    if (form.InvokeRequired)
      form.Invoke(guiCommand);
    else
      guiCommand();
  }
}

ThreadSafeGuiCommand 의 인스턴스로 다른 스레드에 의해 업데이트되는 양식을 제공 하고 다음과 같이 GUI를 업데이트하는 메소드를 정의합니다.

public void SetLabeTextTo(string value)
{
  _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });
}

이런 식으로 GUI가 호출 할 스레드를 업데이트하고 선택적으로 잘 정의 된 시간 (시간 초과)을 기다리는 것이 확실합니다.


1
IsHandleCreated도 확인하면서 여기에 왔습니다. 확인할 다른 속성 중 하나는 IsDisposed입니다. 양식이 삭제되면 Invoke ()를 호출 할 수 없습니다. 백그라운드 스레드가 완료되기 전에 사용자가 양식을 닫은 경우 양식을 배치 할 때 UI를 다시 호출하려고하지 않습니다.
Jon

나는 시작하는 것이 나쁜 생각이라고 말할 것입니다 ... 일반적으로, 당신은 즉시 자식 양식을 보여주고 백그라운드 처리를하는 동안 진행률 표시 줄이나 다른 피드백을 가질 것입니다. 또는 모든 처리를 먼저 수행 한 다음 생성시 결과를 새 양식으로 전달합니다. 동시에 두 가지를 모두 수행하면 일반적으로 약간의 이점이 있지만 유지 관리가 가능한 코드는 훨씬 적습니다.
Phil1970

설명 된 시나리오는 백그라운드 스레드 작업의 진행보기로 사용되는 모달 양식 을 고려합니다 . 모달이어야하므로 Form.ShowDialog () 메서드를 호출하여 표시해야합니다 . 이렇게하면 호출을 따르는 코드가 양식이 닫힐 때까지 실행되지 않습니다. 따라서 주어진 예제와 다르게 백그라운드 스레드를 시작할 수 없다면 (그리고 물론 가능합니다) 백그라운드 스레드가 시작된 후에이 양식을 모달로 표시해야합니다. 이 경우 핸들이 작성되었는지 확인해야합니다. 모달 양식이 필요하지 않으면 또 다른 이야기입니다.
Sume
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.