다른 Thread
Label
을 업데이트하는 가장 간단한 방법은 무엇입니까?
thread1
에서 실행 중인Form
이 있고 그로부터 다른 스레드(thread2
)를 시작하고 있습니다.하지만
thread2
일부 파일을 처리하는 나는 업데이 트하려는Label
상의Form
의 현재 상태와thread2
일의.
어떻게 하면 될까요?
질문자 :CruelIO
다른 Thread
Label
을 업데이트하는 가장 간단한 방법은 무엇입니까?
thread1
에서 실행 중인 Form
이 있고 그로부터 다른 스레드( thread2
)를 시작하고 있습니다.
하지만 thread2
일부 파일을 처리하는 나는 업데이 트하려는 Label
상의 Form
의 현재 상태와 thread2
일의.
어떻게 하면 될까요?
가장 간단한 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
는 완료될 때까지 실행을 차단합니다. 이것은 동기 코드입니다. 질문은 비동기 코드에 대해 묻지 않지만 스택 오버플로 에 대해 배우고 싶을 때 비동기 코드 작성에 대한 내용이 많이 있습니다.
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년 5월 10일 업데이트:
.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 버전보다 훨씬 낫습니다.
컴파일 시간 안전을 위해 이 코드를 개선하는 방법에 대한 추가 제안이 있는 사람이 있으면 의견을 말하십시오!
.NET 4.5 및 C# 5.0 부터 모든 영역 (GUI 포함) 에서 async - await 키워드와 함께 작업 기반 비동기 패턴(TAP) 을 사용해야 합니다.
TAP는 신규 개발에 권장되는 비동기 설계 패턴입니다.
APM(비동기 프로그래밍 모델) 및 EAP(이벤트 기반 비동기 패턴) 대신 (후자는 BackgroundWorker 클래스 포함 )
그런 다음 새로운 개발에 권장되는 솔루션은 다음과 같습니다.
이벤트 핸들러의 비동기 구현(예, 그게 전부입니다):
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"; }
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()); } } }
다음 사항에 유의하십시오.
더 자세한 예를 보려면 Joseph Albahari 의 Future of C#: Good things come to '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..."); } }
.NET 4를 위한 Marc Gravell의 가장 간단한 솔루션 의 변형:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
또는 대신 Action 대리자를 사용하십시오.
control.Invoke(new Action(() => control.Text = "new text"));
두 가지를 비교하려면 여기를 참조하십시오. MethodInvoker와 Control.BeginInvoke에 대한 작업
.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
개체임을 기억하십시오), ObjectDisposedException
이 발생할 가능성이 높습니다.
이 용액을 사용하는 것이다 SynchronizationContext
구체적 SynchronizationContext.Current
로 hamilton.danielb는 (다른 응답이 특정의 제안에 의존 SynchronizationContext
완전히 불필요 구현). 나는 약간 사용하는 자신의 코드를 수정하는 것 SynchronizationContext.Post
대신 SynchronizationContext.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이 설명한 Progress<T>
(생성 시 SynchronizationContext.Current
를 캡처함)를 사용할 수 있습니다.
업데이트가 올바른 스레드에서 발생하는지 확인해야 합니다. 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 ( ... ); } } }
ISynchronizeInvoke
인터페이스를 구현하지 않기 때문에 위의 코드는 WPF 프로젝트에서 작동하지 않습니다.
위의 코드가 Windows 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 스레드가 폴링합니다. 이것은 많은 시나리오에서 사용되는 일반적인 패턴입니다. 당신이 원하는 것은 작업자 스레드의 진행 정보를 표시하는 것뿐이므로 이 솔루션이 마샬링 솔루션에 대한 훌륭한 대안이라는 것을 알게 될 것입니다. 다음과 같은 장점이 있습니다.
Control.Invoke
또는 Control.BeginInvoke
접근 방식과 달리 느슨하게 결합된 상태로 유지됩니다.GUI 스레드에서 메서드를 호출해야 합니다. Control.Invoke를 호출하여 이를 수행할 수 있습니다.
예를 들어:
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");
살베테! 이 질문을 검색한 결과 FrankG 와 Oregon Ghost 의 답변이 가장 쉽고 유용하다는 것을 알았습니다. 이제 Visual Basic으로 코드를 작성하고 변환기를 통해 이 스니펫을 실행했습니다. 그래서 어떻게 되는지 잘 모르겠습니다.
나는 일종의 로깅 디스플레이로 사용하고 있는 updateDiagWindow,
form_Diagnostics,
텍스트 상자가 있는 form_Diagnostics라는 대화 형식을 가지고 있습니다. 모든 스레드에서 텍스트를 업데이트할 수 있어야 했습니다. 추가 줄을 사용하면 창이 자동으로 최신 줄로 스크롤할 수 있습니다.
따라서 이제 스레딩 없이 작동할 것이라고 생각하는 방식으로 전체 프로그램의 어느 곳에서나 디스플레이를 한 줄로 업데이트할 수 있습니다.
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()"는 원하는 만큼 많은 컨트롤을 변경할 수 있는 양식(this) 내의 GUI 수준 메서드입니다. 다른 스레드에서 "updateGUI()"를 호출합니다. 매개변수를 추가하여 값을 전달하거나, 불안정을 유발할 수 있는 액세스 스레드 간에 충돌 가능성이 있는 경우 필요에 따라 잠금이 있는 클래스 범위 변수를 사용할 수 있습니다. 비 GUI 스레드가 시간이 중요한 경우(Brian Gideon의 경고를 염두에 두고) Invoke 대신 BeginInvoke를 사용합니다.
이것은 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!")
그렇지 않으면 원본이 매우 좋은 솔루션입니다.
동일한 문제가 발생했을 때 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(..)
간단히 다음과 같이 사용하십시오.
this.Invoke((MethodInvoker)delegate { progressBar1.Value = e.ProgressPercentage; // runs on UI thread });
대부분의 다른 답변은 이 질문에 대해 약간 복잡하므로(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"; });
내 버전은 재귀 "만트라"의 한 줄을 삽입하는 것입니다.
인수가 없는 경우:
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줄이 아닌 한 줄의 코드).
if(InvokeRequired) {something long}
에서 알 수 있듯이 "이 함수는 다른 스레드에서 호출하는 것이 안전합니다".
이미 존재하는 대리자 Action
사용할 수 있습니다.
private void UpdateMethod() { if (InvokeRequired) { Invoke(new Action(UpdateMethod)); } }
클래스 변수 생성:
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); } }
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; } }
이것은 현재 동시 코드를 작성 하는 데 선호되는 방식인 작업(스레드가 아님)에 대해 작동합니다.
WPF 응용 프로그램에서 가장 간단한 방법은 다음과 같습니다.
this.Dispatcher.Invoke((Action)(() => { // This refers to a form in a WPF application val1 = textBox.Text; // Access the UI }));
예를 들어, 현재 스레드가 아닌 다른 컨트롤에 액세스합니다.
Speed_Threshold = 30; textOutput.Invoke(new EventHandler(delegate { lblThreshold.Text = Speed_Threshold.ToString(); }));
여기서 lblThreshold
는 레이블이고 Speed_Threshold
는 전역 변수입니다.
그리고 또 다른 일반적인 Control 확장 접근 방식..
먼저 Control 유형의 개체에 대한 확장 메서드를 추가 합니다.
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; } );
출처 : http:www.stackoverflow.com/questions/661561/how-do-i-update-the-gui-from-another-thread
모나드란? (0) | 2022.02.24 |
---|---|
varchar와 nvarchar의 차이점은 무엇입니까? (0) | 2022.02.24 |
UNION과 UNION ALL의 차이점은 무엇입니까? (0) | 2022.02.19 |
문자열이 유효한 숫자인지 확인하는 JavaScript의 (내장) 방법 (0) | 2022.02.19 |
매개변수를 사용하는 Bash 별칭을 만드시겠습니까? (0) | 2022.02.19 |