programing

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

showcode 2023. 6. 4. 10:46
반응형

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

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

  • 나는 있습니다Form으로 thread1또 다른 그리다작그나터다시스른니레합를드는부고것로으▁(그다▁another▁thread▁and니)를 시작합니다thread2).

  • 하는 동안에thread2. 업데이트하고 싶은 파일이 있습니다.Label에서.Form…의 thread2의 일.

내가 어떻게 그럴 수 있을까?

가장 간단한 방법은 다음으로 전달되는 익명 메서드입니다.

// 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완료될 때까지 실행을 차단합니다. 동기화 코드입니다.이 질문은 비동기 코드에 대해 묻는 것이 아니라 스택 오버플로에 대해 배우고 싶을 때 비동기 코드를 작성하는 내용이 많습니다.

2경우 하고 .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);

3을 .NET 3.0의 할 수 .Control클래스를 선택하면 다음과 같은 통화가 간소화됩니다.

myLabel.SetPropertyThreadSafe("Text", status);

업데이트 05/10/2010:

.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 및 람다 식을 사용하여 훨씬 더 깨끗하고 단순하며 안전한 구문을 사용할 수 있습니다.

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

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

사람을 하는 것과 을 하는 .Control과 같은 것들이 컴파일될

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

따라서 전달된 속성이 실제로 해당 속성에 속하는지 확인하기 위해 런타임 검사를 추가했습니다.Control않지만 .NET.완벽하지는 않지만 여전히 .NET 2.0 버전보다 훨씬 낫습니다.

컴파일 타임 안전을 위해 이 코드를 개선하는 방법에 대해 추가로 제안할 수 있는 사람이 있다면 의견을 제시해 주십시오!

장시간 작업 처리

.NET 4.5 및 C# 5.0에서는 모든 영역(GUI 포함)에서 비동기-대기 키워드와 함께 TAP(Task-based Asynchronous Pattern)를 사용해야 합니다.

TAP은 새로운 개발을 위한 권장 비동기 설계 패턴입니다.

APM(Asynchronous Programming Model)과 EAP(Event-Based Asynchronous Pattern)(후자에는 BackgroundWorker Class가 포함됨) 대신 사용할 수 있습니다.

그렇다면 새로운 개발을 위한 권장 솔루션은 다음과 같습니다.

  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. 스레드 대신 작업입니다.
  3. 작업이 완료될 때까지 이벤트 핸들러가 완료 상태에 도달하지 못하도록 하고 그 동안 UI 스레드를 차단하지 않도록 하는 async 키워드를 사용할 수 있습니다.
  4. 관심 분리(SoC) 설계 원칙을 지원하고 명시적인 디스패처 및 호출을 필요로 하지 않는 진행률 클래스(IP진행률 인터페이스 참조).작성 위치(여기서는 UI 스레드)의 현재 동기화 컨텍스트를 사용합니다.
  5. 작업 생성 옵션.작업스레드 풀로 대기열에 넣지 않음을 암시하는 긴 실행입니다.

자세한 예는 다음을 참조하십시오.C#미래: 조셉 알바하리의 '기다리는' 사람들에게 좋은 일이 찾아옵니다.

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

.NET 4에 대한 마크 그라벨의 가장 단순한 솔루션의 변형:

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

또는 작업 대리자를 대신 사용합니다.

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

두 가지를 비교하려면 여기를 참조하십시오.메서드 호출자 대 제어를 위한 액션입니다.호출 시작

.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");

이 작업은 일반적으로 다음과 같이 수행해야 합니다.

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에서 실제 컨트롤을 변경하려면 스레드를 교차해야 합니다.레이블이나 진행률 표시줄처럼.

은 간한해은다같습다니음과결책단다를 사용하는 것입니다.Control.Invoke.

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

void updateGUI() {
    // update gui here
}

스레드화 코드는 종종 버그가 있으며 항상 테스트하기 어렵습니다.백그라운드 작업에서 사용자 인터페이스를 업데이트하기 위해 스레드 코드를 작성할 필요는 없습니다.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메서드는 완전히 무시됩니다.

대다수의 응답자가 사용합니다.Control.Invoke그것은 일어날 것을 기다리고 있는 인종 조건입니다.예를 들어, 수락된 답변을 생각해 보십시오.

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

가 사자가직양닫경우은 this.Invoke라고 합니다(예:this는 것은입니다.Form 적어목), ▁anObjectDisposedException해고될 가능성이 높습니다.

은 해책은다같습다니과를 사용하는 입니다.SynchronizationContext으로 구적으로체.SynchronizationContext.Currenthamilton.nb가 시사하는 바와 같이 (다른 대답들은 특정에 의존합니다.SynchronizationContext완전히 불필요한 구현).그의 코드를 약간 수정해서 사용할 것입니다.SynchronizationContext.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 이상에서는 비동기 작업에 작업을 사용해야 합니다.동일한 작업 기반 접근 방식에 대한 Sen-san의 답변(사용)TaskScheduler.FromCurrentSynchronizationContext).

4.NET 4.5를 .Progress<T>합니다.)SynchronizationContext.CurrentRyszard Dżegan이 장시간 실행하는 작업이 여전히 작동하는 동안 UI 코드를 실행해야 하는 경우에 대해 설명한 바와 같이 생성 시).

업데이트가 올바른 스레드인 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 프로젝트에서 수. WPF 프로젝트에서 할 수 없습니다.ISynchronizeInvoke인터페이스

및 및 Forms 및 WPF를 .AsyncOperation,AsyncOperationManager그리고.SynchronizationContext반.

이러한 방식으로 이벤트를 쉽게 제기할 수 있도록 다음과 같이 전화하기만 하면 이벤트를 쉽게 제기할 수 있는 확장 방법을 만들었습니다.

MyEvent.Raise(this, EventArgs.Empty);

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

시나리오의 사소한 점 때문에 실제로 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.Invoke그리고.ISynchronizeInvoke.BeginInvoke하는 것은 가 되지 이 몇 .마셜링 기법을 사용하는 데는 아무런 문제가 없지만, 주의해야 할 몇 가지 주의 사항이 있습니다.

  • 전화하지 않도록 하십시오.BeginInvoke메시지 펌프를 오버런할 수 있습니다.
  • 하기 르기Invoke작업자 스레드에 차단 호출이 있습니다.그러면 해당 스레드에서 수행 중인 작업이 일시적으로 중지됩니다.

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

  • 는 UI와 를 유지합니다.Control.Invoke또는Control.BeginInvoke그들을 긴밀하게 연결시키는 접근.
  • UI 스레드는 작업자 스레드의 진행을 방해하지 않습니다.
  • 작업자 스레드는 UI 스레드가 업데이트하는 시간을 지배할 수 없습니다.
  • UI 및 작업자 스레드가 작업을 수행하는 간격은 독립적으로 유지될 수 있습니다.
  • 작업자 스레드는 UI 스레드의 메시지 펌프를 오버런할 수 없습니다.
  • UI 스레드는 UI가 업데이트되는 시기와 빈도를 지정합니다.

GUI 스레드에서 메서드를 호출해야 합니다.Control(제어)에 전화하면 됩니다.호출합니다.

예:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}

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

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
}

이것은 .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");

살베테!이 질문을 검색해 본 결과, 프랭크 지와 오리건 고스트의 답변이 저에게 가장 유용하다는 것을 알게 되었습니다.저는 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
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().

많은 목적에서 이는 다음과 같이 간단합니다.

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

"serviceGUI()"는 원하는 만큼의 컨트롤을 변경할 수 있는 양식 내의 GUI 수준 방법입니다.다른 스레드에서 "updateGUI()"를 호출합니다.매개 변수를 통과 값에 추가하거나 액세스하는 스레드 간에 매개 변수가 충돌하여 불안정성이 발생할 가능성이 있는 경우 필요에 따라 잠금이 설정된 클래스 범위 변수를 사용할 수 있습니다.시작 사용비GUI 스레드가 시간에 중요한 경우 Invoke 대신 Invoke를 실행합니다(Brian Gideon의 경고를 염두에 둡니다).

가 같은 했을 때,, 해결책을 보다는, 를 더 .MethodInvoker그고리 ah▁blah ah리▁bl.그래서 저는 스스로 해결하기로 결심했습니다.제 솔루션은 다음과 같습니다.

대리인을 다음과 같이 만듭니다.

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(..)여기서 설명하지 말아야 할 방법도 있습니다.

이것은 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"의 결과에 null 검사를 추가합니다.
  2. 정적 유형 안전성을 향상시킵니다.

그렇지 않으면, 원본은 매우 좋은 해결책입니다.

다른 대부분의 답변은 이 질문에 대해 조금 복잡합니다(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"));
}

위의 행이 무엇을 의미하는지는 아직 알 수 없지만 효과가 있습니다.

WinForms의 경우:

솔루션:

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

다음과 같은 것을 사용하면 됩니다.

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

제 버전은 재귀적인 "만트라"의 한 을 삽입하는 것입니다.

인수가 없는 경우:

    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!
    }

바로 그거야.


가지 주장:일반적으로 {} 뒤에 {}을(를) 붙이는 것은 코드 가독성에 좋지 않습니다.if ()입니다.하지만 이 경우에는 일상적인 "맨트라"입니다.이 방법이 프로젝트에서 일관성을 유지한다면 코드 가독성이 깨지지 않습니다.또한 코드가 쓰레기 더미(5줄이 아닌 한 줄의 코드)에서 벗어날 수 있습니다.

바와 같이.if(InvokeRequired) {something long}"이 함수는 다른 스레드에서 호출해도 안전하다"는 것만 알고 있을 뿐입니다.

하는 대리인 ▁use를 사용해도 됩니다.Action:

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

그리고 또 다른 일반적인 제어 확장 접근법은..

먼저 컨트롤 유형의 개체에 대한 확장 메서드를 추가합니다.

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

다른 스레드에서 다음과 같이 호출하여 UI 스레드의 object1이라는 컨트롤에 액세스합니다.

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

..아니면 이런 식으로

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

클래스 변수를 만듭니다.

SynchronizationContext _context;

UI를 만드는 생성자에서 설정합니다.

var _context = SynchronizationContext.Current;

레이블을 업데이트하려는 경우:

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

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

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

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

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

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

WPF 애플리케이션에서 가장 간단한 방법은 다음과 같습니다.

this.Dispatcher.Invoke((Action)(() =>
{
    // This refers to a form in a WPF application 
    val1 = textBox.Text; // Access the UI 
}));

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

그런 다음 결과가 준비되면 다른 작업(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;
  }
}

이것은 현재 선호되는 동시 코드 작성 방법인 태스크(스레드가 아님)에 적용됩니다.

예를 들어, 현재 스레드가 아닌 다른 컨트롤에 액세스합니다.

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

거기서lblThreshold이며 레블및입니다.Speed_Threshold전역 변수입니다.

언급URL : https://stackoverflow.com/questions/661561/how-do-i-update-the-gui-from-another-thread

반응형