시나리오가 있습니다. (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 번 라인의 실행으로 인한 것 같습니다. 로딩 작업은 부모 스레드에 의해 다시 수행되며 내가 생성 한 세 번째 스레드는 아닙니다.
나는 이것이 옳은지인지 아닌지를 알지 못한다. 스레딩을 처음 사용합니다.
이 문제를 어떻게 해결하고 라인 # 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의 경우 권장 솔루션은 다음과 같습니다 .
답변
당신은 사용하고자하는 Invoke
또는 BeginInvoke
작품의 최소한의 조각의 UI를 변경해야합니다. “무거운”방법은 다른 스레드 (예 :를 통해 BackgroundWorker
) 에서 실행 한 다음 Control.Invoke
/ Control.BeginInvoke
를 사용하여 UI를 업데이트해야합니다. 그렇게하면 UI 스레드가 UI 이벤트 등을 자유롭게 처리 할 수 있습니다.
기사가 현장에 도착 하기 전에 작성되었지만 WinForms 예제에 대한 스레딩 기사 를 참조하십시오 . 그런 점에서 업데이트하지 않았습니다. 콜백을 약간 단순화합니다.BackgroundWorker
BackgroundWorker
답변
너무 늦었 어 그러나 오늘날에도 크로스 스레드 컨트롤에 액세스하는 데 문제가 있습니까? 이것은 날짜까지 가장 짧은 답변입니다 : P
Invoke(new Action(() =>
{
label1.Text = "WooHoo!!!";
}));
이것은 스레드에서 양식 컨트롤에 액세스하는 방법입니다.
답변
나는이 문제를 FileSystemWatcher
겪었고 다음 코드가 문제를 해결한다는 것을 알았습니다.
fsw.SynchronizingObject = this
그런 다음 컨트롤은 현재 폼 개체를 사용하여 이벤트를 처리하므로 동일한 스레드에있게됩니다.
답변
양식과 관련된 모든 메소드 내에서 너무 장황하고 불필요하게 처리 해야하는 체크 앤 호출 코드를 발견했습니다. 다음은 간단한 확장 방법으로 완전히 제거 할 수 있습니다.
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");
더 이상 혼란스럽지 않습니다-간단합니다.
답변
.NET의 컨트롤은 일반적으로 스레드로부터 안전하지 않습니다. 즉, 스레드가 아닌 스레드에서 컨트롤에 액세스하면 안됩니다. 이 문제를 해결하려면 두 번째 샘플이 시도하는 컨트롤 을 호출 해야합니다 .
그러나 귀하의 경우에는 장기 실행 방법을 기본 스레드로 다시 전달하면됩니다. 물론, 그것은 당신이 정말로 원하는 것이 아닙니다. 메인 스레드에서 수행하는 모든 작업이 여기 저기에서 빠른 속성을 설정하도록이 부분을 다시 생각해야합니다.