504

나는 시나리오가있다. (Windows Forms, C #, .NET)

  1. 사용자 정의 컨트롤을 호스팅하는 기본 폼이 있습니다.
  2. 사용자 정의 컨트롤은 무거운 데이터 작업을 수행하므로 직접 호출하면UserControl_Load메서드를 호출하면로드 메서드 실행 기간 동안 UI가 응답하지 않게됩니다.
  3. 이를 극복하기 위해 다른 스레드에서 데이터를로드합니다 (가능한 한 적은 기존 코드를 변경하려고합니다)
  4. 데이터를로드 할 백그라운드 작업자 스레드를 사용하고 작업이 완료되면 응용 프로그램에 작업이 완료되었음을 알립니다.
  5. 이제 진짜 문제가 생겼습니다. 모든 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의 실행 효과는 무엇입니까?

상황은 이것이다.: 컨트롤의 값을 기반으로 전역 변수에 데이터를로드하려고합니다. 자식 스레드에서 컨트롤의 값을 변경하고 싶지 않습니다. 나는 자식 스레드에서 그걸하지 않을거야.

따라서 데이터베이스에서 해당 데이터를 가져올 수 있도록 값에 액세스 만합니다.

20 답변


383

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


  • 의지control.BeginInvoke너무 일해? - newbieguy
  • C #프로그래밍을 수행 한 이후로 지금까지는 시간이 지났지 만 MSDN 기사와 패치 스틱 지식을 기반으로 한 것처럼 보입니다. - Jeff Hubbard
  • 차이점은 Invoke ()가 동 기적으로 실행되는 동안 BeginInvoke ()는 비동기입니다.stackoverflow.com/questions/229554/… - frzsombor

148

UI 스레딩 모델

자세한 내용은스레딩 모델UI 응용 프로그램에서 기본 개념을 이해할 수 있습니다. 이 링크는 WPF 스레딩 모델을 설명하는 페이지로 이동합니다. 그러나 Windows Forms는 동일한 아이디어를 사용합니다.

UI 스레드

  • 액세스가 허용되는 스레드 (UI 스레드)는 하나뿐입니다.System.Windows.Forms.Control및 그 서브 클래스 멤버.
  • 회원에게 액세스하려고 시도했습니다.System.Windows.Forms.ControlUI 스레드와 다른 스레드에서 스레드 간 예외가 발생할 수 있습니다.
  • 스레드가 하나뿐이므로 모든 UI 작업은 해당 스레드에 작업 항목으로 대기합니다.

enter image description here

enter image description here

BeginInvoke 및 Invoke 메서드

  • 호출되는 메소드의 컴퓨팅 오버 헤드는 이벤트 핸들러 메소드의 컴퓨팅 오버 헤드뿐만 아니라 작아야하며 사용자 스레드를 처리하는 UI 스레드가 사용되기 때문에 이벤트 처리 메소드의 컴퓨팅 오버 헤드가 작아야합니다. 관계없이System.Windows.Forms.Control.Invoke또는System.Windows.Forms.Control.BeginInvoke.
  • 값 비싼 연산을 수행하려면 항상 별도의 스레드를 사용해야합니다. .NET 2.0 이후BackgroundWorkerWindows Forms에서 값 비싼 연산을 수행하는 데 전념합니다. 그러나 새로운 솔루션에서는 설명한대로 async-await 패턴을 사용해야합니다이리.
  • 용도System.Windows.Forms.Control.Invoke또는System.Windows.Forms.Control.BeginInvoke메소드 만 사용자 인터페이스를 업데이트합니다. 계산량이 많은 경우이를 사용하면 응용 프로그램이 차단됩니다.

enter image description here

호출

enter image description here

BeginInvoke

enter image description here

코드 솔루션

질문에 대한 답변보기어떻게 C에서 다른 스레드에서 GUI를 업데이 트하려면 #?. C #5.0 및 .NET 4.5의 경우 권장되는 솔루션은 다음과 같습니다.이리.


69

UI를 변경하는 데 필요한 최소한의 작업에만 Invoke 또는 BeginInvoke를 사용하려고합니다. "무거운"메서드는 다른 스레드에서 (예 : BackgroundWorker를 통해) 실행해야하지만 UI 만 업데이트하려면 Control.Invoke / Control.BeginInvoke를 사용해야합니다. 그렇게하면 UI 스레드가 UI 이벤트 등을 자유롭게 처리 할 수 있습니다.

내 그림보기스레딩 기사a를 위해WinForms 예제- BackgroundWorker가 현장에 도착하기 전에 기사가 작성되었지만 나는 그 점을 업데이트하지 않았습니다. BackgroundWorker는 단순히 콜백을 단순화합니다.


  • 여기 내 상태. 나는 심지어 UI를 바꾸지 않는다. 자식 스레드에서 현재 값에 액세스하고 있습니다. 구현할 제안 - Prerak K
  • 속성에 액세스하는 경우에도 UI 스레드로 마샬링해야합니다. 값에 액세스 할 때까지 메소드가 계속 진행할 수없는 경우 값을 반환하는 위임자를 사용할 수 있습니다. 하지만 네, UI 스레드를 통해 이동하십시오. - Jon Skeet
  • 안녕 존, 나는 올바른 방향으로 나를 향하고있다. 그래, 나는 그것없이 가치를 필요로한다. 나는 앞으로 나아갈 수 없다. 그걸 ' 값을 반환하는 대리자 사용 ('). 감사 - Prerak K
  • Func < string > ;:와 같은 대리인을 사용하십시오. string text = textbox1.Invoke ((Func < string >) = > textbox1.Text); (C #3.0을 사용한다고 가정 할 경우 - 그렇지 않으면 익명 메소드를 사용할 수 있습니다.) - Jon Skeet

38

나는이 문제를 가지고있다.FileSystemWatcher다음 코드가 문제를 해결한다는 것을 발견했습니다.

fsw.SynchronizingObject = this

그런 다음 컨트롤은 현재 양식 객체를 사용하여 이벤트를 처리하므로 동일한 스레드에 있습니다.


  • 이것은 나의 베이컨을 저장했다. VB.NET에서 나는 사용했다..SynchronizingObject = Me - codingcoding

14

.NET의 컨트롤은 일반적으로 스레드로부터 안전하지 않습니다. 즉, 살아있는 스레드가 아닌 다른 스레드에서 컨트롤에 액세스해서는 안됩니다. 이 문제를 해결하려면~을 부르다두 번째 샘플이 시도하고있는 컨트롤입니다.

그러나, 당신이 한 모든 일은 장기 실행 방법을 메인 쓰레드로 되돌려 보내는 것입니다. 물론, 그것은 당신이하고 싶은 것이 아닙니다. 메인 스레드에서 수행중인 모든 작업이 여기 저기에 빠른 속성을 설정하도록이 것을 조금 재검토해야합니다.


14

나는 폼에 관련된 모든 메소드에서 너무 장황하고 불필요한 코드가 필요하다는 check-and-invoke 코드를 발견했다. 다음은 완전히 확장하지 못하게하는 간단한 확장 메소드입니다.

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

더 이상 주위를 어지럽히 지 말라.


  • ' 이리 - Rawat
  • @ 케어t이 경우에는textbox1- 인수로 전달되었습니다. - Rob

14

나는 그것의 너무 늦은 것을 지금 알고있다. 그러나 오늘날에도 크로스 스레드 컨트롤에 액세스하는 데 문제가 있습니까? 이것은 날짜까지 가장 짧은 대답입니다 : P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

이것은 스레드에서 양식 컨트롤에 액세스하는 방법입니다.


  • 이것은 나를 준다.Invoke or BeginInvoke cannot be called on a control until the window handle has been created. 나는 그것을 해결했다.이리 - rupweb

11

UI 크로스 스레딩 문제에 대한 가장 깨끗하고 (적절한) 솔루션은 SynchronizationContext를 사용하는 것입니다.다중 스레드 응용 프로그램에서 UI 호출을 동기화기사, 그것 아주 잘 설명합니다.


7

다른 스레드에서 객체를 수정하는 가장 간단한 방법을 따릅니다.

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


7

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

Extension 메소드에 Try / Catch 문으로 래핑하는 것과 같은 다른 작업을 추가하여 호출자가 완료 후에 리턴 할 유형을 호출자가 알 수 있도록합니다. 호출자에 대한 예외 콜백은 다음과 같습니다.

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


6

Backgroundworker 예제를 살펴볼 필요가있다.

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx특히 UI 레이어와 어떻게 상호 작용하는지. 귀하의 게시물을 기반으로, 이것은 귀하의 문제에 대한 답변 것 같습니다.


6

나는 xamarin stuidio 외부의 비주얼 스튜디오 winforms 프로토 타입 프로젝트에서 iOS-Phone 모노 터치 앱 컨트롤러를 프로그래밍하는 동안이 필요성을 발견했습니다. 가능하면 VS 스튜디오에서 xamarin 스튜디오 이상으로 프로그램하는 것을 선호하며 컨트롤러를 전화 프레임 워크에서 완전히 분리해야했습니다. Android 및 Windows Phone과 같은 다른 프레임 워크에이를 구현하면 나중에 사용하기가 훨씬 쉬워집니다.

GUI가 이벤트에 응답 할 수있는 솔루션을 원했습니다. 모든 버튼을 클릭 할 때 크로스 스레딩 전환 코드를 처리해야하는 부담이 없었습니다. 기본적으로 클래스 컨트롤러가이를 처리하여 클라이언트 코드를 간단하게 유지하게합니다. GUI의 많은 이벤트를 가질 수 있습니다. 예를 들어 클래스의 한 위치에서 처리 할 수있는 것처럼 깨끗합니다. 나는 멀티 테딩 전문가가 아니며, 이것이 결함이 있는지 알려주지.

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

GUI 양식은 컨트롤러가 비동기 작업을 실행하는 것을 인식하지 못합니다.

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


6

이것은이 오류를 해결하기위한 권장 방법은 아니지만 신속하게 억제 할 수 있으므로 작업을 수행 할 수 있습니다. 프로토 타입이나 데모에서는이 방법을 선호합니다. 더하다

CheckForIllegalCrossThreadCalls = false

...에서Form1()생성자.


4

작업중인 객체가 가지고 있지 않은 다른 방법은 다음과 같습니다.

(InvokeRequired)

기본 폼 이외의 클래스에서 주 폼을 사용하여 작업하는 경우 기본 폼에 있지만 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;
}

위와 동일하게 작동하지만 invokerequired 객체가 없지만 MainForm에 대한 액세스 권한이있는 경우에는 다른 접근 방식입니다.


3

this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));


3

이전 답변과 동일한 줄에 따라, 매우 짧은 추가로 크로스 스레드 인보 케이션 예외없이 모든 컨트롤 속성을 사용할 수 있습니다.

도우미 방법

/// <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;
}

샘플 사용법

// 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;
}


3

예를 들어, UI 스레드의 컨트롤에서 텍스트를 가져 오는 방법은 다음과 같습니다.

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


1

같은 질문 : how-to-update-the-gui-from-another-thread-in-c

두 가지 방법:

  1. e.result의 값을 반환하고 backgroundWorker_RunWorkerCompleted 이벤트에서 yout 텍스트 상자 값을 설정하는 데 사용합니다.

  2. 별도의 클래스에서 이러한 종류의 값을 보유하도록 일부 변수를 선언하십시오 (데이터 보유자로 작동 함). 이 클래스의 정적 인스턴스를 만들면 모든 스레드에서 액세스 할 수 있습니다.

예:

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


1

액션 y; // 클래스 내부에서 선언

label1.Invoke (y = () => label1.Text = "text");


-2

크로스 스레드 작업에는 두 가지 옵션이 있습니다.

Control.InvokeRequired Property 

두 번째는 사용하는 것입니다.

SynchronizationContext Post Method

Control.InvokeRequired는 Control 클래스에서 상속 된 작업 컨트롤이 SynchronizationContext를 어디에서나 사용할 수있는 경우에만 유용합니다. 유용한 정보는 다음과 같습니다.

크로스 스레드 업데이트 UI | .그물

SynchronizationContext를 사용하여 스레드 업데이트 UI 교차 | .그물

연결된 질문


관련된 질문

최근 질문