크로스 스레드 작업이 유효하지 않음 : 생성 된 스레드가 아닌 스레드에서 액세스 된 제어
시나리오가 있습니다. (Windows Forms, C #, .NET)
- 일부 사용자 컨트롤을 호스팅하는 기본 양식이 있습니다.
- 사용자 정의 컨트롤은 무거운 데이터 작업을 수행하므로
UserControl_Load메서드를 직접 호출하면 로드 메서드 실행 기간 동안 UI가 응답하지 않게됩니다. - 이를 극복하기 위해 다른 스레드에서 데이터를로드합니다 (가능한 한 기존 코드를 변경하려고 시도).
- 데이터를로드 할 백그라운드 작업자 스레드를 사용했으며 완료되면 응용 프로그램에 작업이 완료되었음을 알립니다.
- 이제 진짜 문제가 생겼습니다. 모든 UI (기본 양식 및 하위 사용자 컨트롤)는 기본 기본 스레드에서 생성되었습니다. usercontrol의 LOAD 메서드에서 userControl의 일부 컨트롤 (예 : 텍스트 상자) 값을 기반으로 데이터를 가져옵니다.
의사 코드는 다음과 같습니다.
코드 1
UserContrl1_LoadDataMethod()
{
if (textbox1.text == "MyName") // This gives exception
{
//Load data corresponding to "MyName".
//Populate a globale variable List<string> which will be binded to grid at some later stage.
}
}
그것이 준 예외는
크로스 스레드 작업이 유효하지 않음 : 생성 된 스레드가 아닌 스레드에서 액세스 된 제어입니다.
이것에 대해 더 많이 알기 위해 인터넷 검색을했고 다음 코드를 사용하는 것과 같은 제안이 나왔습니다.
코드 2
UserContrl1_LoadDataMethod()
{
if (InvokeRequired) // Line #1
{
this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
return;
}
if (textbox1.text == "MyName") // Now it wont give an exception
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be binded to grid at some later stage
}
}
하지만하지만 ... 다시 원점으로 돌아온 것 같습니다. 응용 프로그램이 다시 응답하지 않습니다. 1 번 라인 if 조건의 실행 때문인 것 같습니다. 로딩 작업은 내가 생성 한 세 번째 스레드가 아니라 부모 스레드에 의해 다시 수행됩니다.
내가이 옳고 그름을인지했는지는 모르겠다. 스레딩이 처음입니다.
이 문제를 어떻게 해결하고 라인 # 1 if 블록의 실행 효과는 무엇입니까?
상황은 다음과 같습니다. 컨트롤 값에 따라 전역 변수에 데이터를로드하고 싶습니다. 자식 스레드에서 컨트롤 값을 변경하고 싶지 않습니다. 나는 자식 스레드에서 결코 그것을하지 않을 것입니다.
따라서 해당 데이터를 데이터베이스에서 가져올 수 있도록 값에만 액세스합니다.
당으로 Prerak K의 업데이트 코멘트 (삭제 이후) :
질문을 제대로 제시하지 않은 것 같습니다.
상황은 다음과 같습니다. 컨트롤 값을 기반으로 전역 변수에 데이터를로드하고 싶습니다. 자식 스레드에서 컨트롤 값을 변경하고 싶지 않습니다. 나는 자식 스레드에서 결코 그것을하지 않을 것입니다.
따라서 데이터베이스에서 해당 데이터를 가져올 수 있도록 값에만 액세스합니다.
원하는 솔루션은 다음과 같아야합니다.
UserContrl1_LOadDataMethod()
{
string name = "";
if(textbox1.InvokeRequired)
{
textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
}
if(name == "MyName")
{
// do whatever
}
}
컨트롤의 스레드로 다시 전환 하기 전에 별도의 스레드에서 심각한 처리를 수행하십시오 . 예를 들면 :
UserContrl1_LOadDataMethod()
{
if(textbox1.text=="MyName") //<<======Now it wont give exception**
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be
//bound to grid at some later stage
if(InvokeRequired)
{
// after we've done all the processing,
this.Invoke(new MethodInvoker(delegate {
// load the control with the appropriate data
}));
return;
}
}
}
UI의 스레딩 모델
기본 개념을 이해하려면 UI 애플리케이션 의 스레딩 모델 을 읽어 보세요. 링크는 WPF 스레딩 모델을 설명하는 페이지로 이동합니다. 그러나 Windows Forms는 동일한 아이디어를 사용합니다.
UI 스레드
- System.Windows.Forms.Control 및 해당 하위 클래스 멤버 에 액세스 할 수있는 스레드 (UI 스레드)는 하나뿐입니다 .
- UI 스레드가 아닌 다른 스레드에서 System.Windows.Forms.Control의 멤버에 액세스하려고 하면 스레드 간 예외가 발생합니다.
- 스레드가 하나뿐이므로 모든 UI 작업은 해당 스레드의 작업 항목으로 대기열에 추가됩니다.

- UI 스레드에 대한 작업이 없으면 UI와 관련이없는 컴퓨팅에서 사용할 수있는 유휴 갭이 있습니다.
- 언급 된 간격을 사용하려면 System.Windows.Forms.Control.Invoke 또는 System.Windows.Forms.Control.BeginInvoke 메서드를 사용 합니다.

BeginInvoke 및 Invoke 메서드
- 호출되는 메서드의 컴퓨팅 오버 헤드는 사용자 입력을 처리하는 것과 동일한 UI 스레드가 사용되기 때문에 이벤트 처리기 메서드의 컴퓨팅 오버 헤드뿐만 아니라 작아야합니다. 이것이 System.Windows.Forms.Control.Invoke 또는 System.Windows.Forms.Control.BeginInvoke 인지에 관계없이 .
- 비용이 많이 드는 연산을 수행하려면 항상 별도의 스레드를 사용하십시오. .NET 2.0 BackgroundWorker 는 Windows Forms에서 비용이 많이 드는 작업을 수행하는 데 전념하고 있습니다. 그러나 새 솔루션에서는 여기에 설명 된대로 async-await 패턴을 사용해야합니다 .
- System.Windows.Forms.Control.Invoke 또는 System.Windows.Forms.Control.BeginInvoke 메서드는 사용자 인터페이스를 업데이트하는 데만 사용 합니다. 무거운 계산에 사용하면 응용 프로그램에서 다음을 차단합니다.

호출
- System.Windows.Forms.Control.Invoke 는 호출 된 메서드가 완료 될 때까지 별도의 스레드를 대기시킵니다.

BeginInvoke
- System.Windows.Forms.Control.BeginInvoke 는 호출 된 메서드가 완료 될 때까지 별도의 스레드가 대기하도록하지 않습니다.

코드 솔루션
질문에 대한 답변을 읽으십시오 C #의 다른 스레드에서 GUI를 업데이트하는 방법? . C # 5.0 및 .NET 4.5의 경우 권장되는 솔루션은 여기 입니다.
UI를 변경하는 데 필요한 최소한의 작업에만 Invoke 또는 BeginInvoke를 사용하려고합니다. "무거운"메서드는 다른 스레드 (예 : BackgroundWorker를 통해)에서 실행되어야하지만 Control.Invoke / Control.BeginInvoke를 사용하여 UI를 업데이트해야합니다. 그러면 UI 스레드가 UI 이벤트 등을 자유롭게 처리 할 수 있습니다.
내 스레딩 기사 에서 WinForms 예제를 참조하십시오.이 기사는 BackgroundWorker가 현장에 도착하기 전에 작성되었지만 그 점에서 업데이트하지 않은 것 같습니다. BackgroundWorker는 콜백을 약간 단순화합니다.
이 문제가 발생 FileSystemWatcher했으며 다음 코드로 문제가 해결되었음을 알았습니다.
fsw.SynchronizingObject = this
그런 다음 컨트롤은 현재 양식 개체를 사용하여 이벤트를 처리하므로 동일한 스레드에있게됩니다.
지금은 너무 늦었다는 것을 알고 있습니다. 그러나 오늘날에도 크로스 스레드 컨트롤에 액세스하는 데 문제가 있다면? 이것은 날짜까지 가장 짧은 답변입니다 : P
Invoke(new Action(() =>
{
label1.Text = "WooHoo!!!";
}));
이것이 스레드에서 양식 컨트롤에 액세스하는 방법입니다.
.NET의 컨트롤은 일반적으로 스레드로부터 안전하지 않습니다. 즉, 컨트롤이있는 스레드가 아닌 다른 스레드에서 컨트롤에 액세스해서는 안됩니다. 이 문제를 해결하려면 두 번째 샘플이 시도하는 컨트롤 을 호출 해야합니다 .
그러나 귀하의 경우에는 장기 실행 메서드를 주 스레드로 다시 전달하기 만하면됩니다. 물론 그것은 당신이 원하는 것이 아닙니다. 메인 스레드에서 수행하는 모든 작업은 여기 저기에서 빠른 속성을 설정하는 것뿐입니다.
나는 너무 장황하고 불필요하기 위해 양식과 관련된 모든 메소드 내에서 흩어져 있어야하는 확인 및 호출 코드를 발견했습니다. 다음은 완전히 제거 할 수있는 간단한 확장 방법입니다.
public static class Extensions
{
public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del)
where TControlType : Control
{
if (control.InvokeRequired)
control.Invoke(new Action(() => del(control)));
else
del(control);
}
}
그런 다음 간단히 이렇게 할 수 있습니다.
textbox1.Invoke(t => t.Text = "A");
더 이상 어지럽히 지 않고 간단합니다.
UI 크로스 스레딩 문제에 대한 가장 깔끔하고 적절한 솔루션은 SynchronizationContext를 사용 하는 것입니다. 다중 스레드 애플리케이션 문서 에서 UI에 대한 호출 동기화를 참조하세요 .
Async / Await 및 콜백을 사용한 새로운 모습. 프로젝트에서 확장 메서드를 유지하는 경우 한 줄의 코드 만 필요합니다.
/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
/// <summary>
/// No more delegates, background workers etc. just one line of code as shown below
/// Note it is dependent on the XTask class shown next.
/// </summary>
public async void ExampleMethod()
{
//Still on GUI/Original Thread here
//Do your updates before the next line of code
await XTask.RunAsync(() =>
{
//Running an asynchronous task here
//Cannot update GUI Thread here, but can do lots of work
});
//Can update GUI/Original thread on this line
}
}
/// <summary>
/// A class containing extension methods for the Task class
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
/// <summary>
/// RunAsync is an extension method that encapsulates the Task.Run using a callback
/// </summary>
/// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
/// <returns></returns>
public async static Task RunAsync(Action Code)
{
await Task.Run(() =>
{
Code();
});
return;
}
}
Try / Catch 문으로 래핑하는 등 Extension 메서드에 다른 항목을 추가하여 호출자가 완료 후 반환 할 유형, 호출자에 대한 예외 콜백을 알려줄 수 있습니다.
Try Catch, 자동 예외 로깅 및 콜백 추가
/// <summary>
/// Run Async
/// </summary>
/// <typeparam name="T">The type to return</typeparam>
/// <param name="Code">The callback to the code</param>
/// <param name="Error">The handled and logged exception if one occurs</param>
/// <returns>The type expected as a competed task</returns>
public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
{
var done = await Task<T>.Run(() =>
{
T result = default(T);
try
{
result = Code("Code Here");
}
catch (Exception ex)
{
Console.WriteLine("Unhandled Exception: " + ex.Message);
Console.WriteLine(ex.StackTrace);
Error(ex);
}
return result;
});
return done;
}
public async void HowToUse()
{
//We now inject the type we want the async routine to return!
var result = await RunAsync<bool>((code) => {
//write code here, all exceptions are logged via the wrapped try catch.
//return what is needed
return someBoolValue;
},
error => {
//exceptions are already handled but are sent back here for further processing
});
if (result)
{
//we can now process the result because the code above awaited for the completion before
//moving to this statement
}
}
Backgroundworker 예제를 살펴보아야합니다.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx 특히 UI 레이어와 상호 작용하는 방법. 귀하의 게시물에 따르면 귀하의 문제에 대한 답변 인 것 같습니다.
다른 스레드에서 객체를 수정하는 가장 간단한 방법을 따르십시오.
using System.Threading.Tasks;
using System.Threading;
namespace TESTE
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Action<string> DelegateTeste_ModifyText = THREAD_MOD;
Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
}
private void THREAD_MOD(string teste)
{
textBox1.Text = teste;
}
}
}
This is not the recommended way to solve this error but you can suppress it quickly, it will do the job . I prefer this for prototypes or demos . add
CheckForIllegalCrossThreadCalls = false
in Form1() constructor .
I found a need for this while programming an iOS-Phone monotouch app controller in a visual studio winforms prototype project outside of xamarin stuidio. Preferring to program in VS over xamarin studio as much as possible, I wanted the controller to be completely decoupled from the phone framework. This way implementing this for other frameworks like Android and Windows Phone would be much easier for future uses.
I wanted a solution where the GUI could respond to events without the burden of dealing with the cross threading switching code behind every button click. Basically let the class controller handle that to keep the client code simple. You could possibly have many events on the GUI where as if you could handle it in one place in the class would be cleaner. I am not a multi theading expert, let me know if this is flawed.
public partial class Form1 : Form
{
private ExampleController.MyController controller;
public Form1()
{
InitializeComponent();
controller = new ExampleController.MyController((ISynchronizeInvoke) this);
controller.Finished += controller_Finished;
}
void controller_Finished(string returnValue)
{
label1.Text = returnValue;
}
private void button1_Click(object sender, EventArgs e)
{
controller.SubmitTask("Do It");
}
}
The GUI form is unaware the controller is running asynchronous tasks.
public delegate void FinishedTasksHandler(string returnValue);
public class MyController
{
private ISynchronizeInvoke _syn;
public MyController(ISynchronizeInvoke syn) { _syn = syn; }
public event FinishedTasksHandler Finished;
public void SubmitTask(string someValue)
{
System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
}
private void submitTask(string someValue)
{
someValue = someValue + " " + DateTime.Now.ToString();
System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.
if (Finished != null)
{
if (_syn.InvokeRequired)
{
_syn.Invoke(Finished, new object[] { someValue });
}
else
{
Finished(someValue);
}
}
}
}
Here is an alternative way if the object you are working with doesn't have
(InvokeRequired)
This is useful if you are working with the main form in a class other than the main form with an object that is in the main form, but doesn't have InvokeRequired
delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);
private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}
public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
objectWithoutInvoke.Text = text;
}
It works the same as above, but it is a different approach if you don't have an object with invokerequired, but do have access to the MainForm
Along the same lines as previous answers, but a very short addition that Allows to use all Control properties without having cross thread invokation exception.
Helper Method
/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
{
a();
}));
else return false;
return true;
}
Sample Usage
// usage on textbox
public void UpdateTextBox1(String text)
{
//Check if invoke requied if so return - as i will be recalled in correct thread
if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
textBox1.Text = ellapsed;
}
//Or any control
public void UpdateControl(Color c, String s)
{
//Check if invoke requied if so return - as i will be recalled in correct thread
if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
myControl.Text = s;
myControl.BackColor = c;
}
this.Invoke(new MethodInvoker(delegate
{
//your code here;
}));
For example to get the text from a Control of the UI thread:
Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String
Private Function GetControlText(ByVal ctl As Control) As String
Dim text As String
If ctl.InvokeRequired Then
text = CStr(ctl.Invoke(
New GetControlTextInvoker(AddressOf GetControlText), ctl))
Else
text = ctl.Text
End If
Return text
End Function
Same question : how-to-update-the-gui-from-another-thread-in-c
Two Ways:
Return value in e.result and use it to set yout textbox value in backgroundWorker_RunWorkerCompleted event
Declare some variable to hold these kind of values in a separate class (which will work as data holder) . Create static instance of this class adn you can access it over any thread.
Example:
public class data_holder_for_controls
{
//it will hold value for your label
public string status = string.Empty;
}
class Demo
{
public static data_holder_for_controls d1 = new data_holder_for_controls();
static void Main(string[] args)
{
ThreadStart ts = new ThreadStart(perform_logic);
Thread t1 = new Thread(ts);
t1.Start();
t1.Join();
//your_label.Text=d1.status; --- can access it from any thread
}
public static void perform_logic()
{
//put some code here in this function
for (int i = 0; i < 10; i++)
{
//statements here
}
//set result in status variable
d1.status = "Task done";
}
}
Action y; //declared inside class
label1.Invoke(y=()=>label1.Text="text");
Simply use this:
this.Invoke((MethodInvoker)delegate
{
YourControl.Property= value; // runs thread safe
});
크로스 스레드 작업에는 두 가지 옵션이 있습니다.
Control.InvokeRequired Property
두 번째는
SynchronizationContext Post Method
Control.InvokeRequired는 Control 클래스에서 상속 된 컨트롤을 작업 할 때만 유용하지만 SynchronizationContext는 어디에서나 사용할 수 있습니다. 유용한 정보는 다음 링크와 같습니다.
'Programing' 카테고리의 다른 글
| Ruby에서 숫자 배열을 합하는 방법은 무엇입니까? (0) | 2020.10.04 |
|---|---|
| Pytz 시간대 목록이 있습니까? (0) | 2020.10.04 |
| 함수를 C에서 매개 변수로 어떻게 전달합니까? (0) | 2020.10.04 |
| 데이터베이스 항목에 대한 소스 제어를 사용합니까? (0) | 2020.10.03 |
| 힘내에서 풀이 필요한지 확인 (0) | 2020.10.03 |