내 Win 양식에 대한 이벤트 콜백을 스레드로부터 안전하게 만들려면 어떻게 해야 합니까?

StackOverflow https://stackoverflow.com/questions/6184

문제

양식 내에서 개체에 대한 이벤트를 구독하면 기본적으로 콜백 메서드에 대한 제어권이 이벤트 소스에 넘겨집니다.해당 이벤트 소스가 다른 스레드에서 이벤트를 트리거하도록 선택하는지 여부는 알 수 없습니다.

문제는 콜백이 호출될 때 양식이 실행된 스레드가 아닌 다른 스레드에서 이벤트 콜백이 호출된 경우 해당 컨트롤이 예외를 발생시키기 때문에 양식에 업데이트 컨트롤을 만들 수 있다고 가정할 수 없다는 것입니다.

도움이 되었습니까?

해결책

Simon의 코드를 약간 단순화하려면 내장된 일반 Action 대리자를 사용할 수 있습니다.실제로 필요하지 않은 여러 대리자 유형을 사용하여 코드를 복잡하게 만드는 일이 줄어듭니다.또한 .NET 3.5에서는 Invoke 메서드에 params 매개 변수를 추가하여 임시 배열을 정의할 필요가 없습니다.

void SomethingHappened(object sender, EventArgs ea)
{
   if (InvokeRequired)
   {
      Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
      return;
   }

   textBox1.Text = "Something happened";
}

다른 팁

주요 사항은 다음과 같습니다.

  1. 생성된 스레드(양식 스레드)가 아닌 다른 스레드에서는 UI 제어 호출을 수행할 수 없습니다.
  2. 대리자 호출(예: 이벤트 후크)은 이벤트를 발생시키는 개체와 동일한 스레드에서 트리거됩니다.

따라서 일부 작업을 수행하는 별도의 "엔진" 스레드가 있고 UI에 반영될 수 있는 상태 변경(예: 진행률 표시줄 등)을 감시하는 UI가 있는 경우 문제가 있는 것입니다.엔진 화재는 Form에 의해 연결된 객체 변경 이벤트입니다.하지만 엔진에 등록된 Form의 콜백 대리자는 Form의 스레드가 아닌 엔진의 스레드에서 호출됩니다.따라서 해당 콜백에서 컨트롤을 업데이트할 수 없습니다.맙소사!

BeginInvoke 구출하러 온다.모든 콜백 메소드에서 이 간단한 코딩 모델을 사용하면 문제가 없는지 확인할 수 있습니다.

private delegate void EventArgsDelegate(object sender, EventArgs ea);

void SomethingHappened(object sender, EventArgs ea)
{
   //
   // Make sure this callback is on the correct thread
   //
   if (this.InvokeRequired)
   {
      this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea });
      return;
   }

   //
   // Do something with the event such as update a control
   //
   textBox1.Text = "Something happened";
}

정말 간단합니다.

  1. 사용 호출필수 이 콜백이 올바른 스레드에서 발생했는지 확인합니다.
  2. 그렇지 않은 경우 동일한 매개변수를 사용하여 올바른 스레드에서 콜백을 다시 호출하십시오.다음을 사용하여 메서드를 다시 호출할 수 있습니다. 부르다 (차단) 또는 BeginInvoke (비차단) 메소드.
  3. 다음에 함수가 호출될 때, 호출필수 이제 올바른 스레드에 있고 모두가 행복하기 때문에 false를 반환합니다.

이는 이 문제를 해결하고 다중 스레드 이벤트 콜백으로부터 양식을 안전하게 만드는 매우 간단한 방법입니다.

이 시나리오에서는 익명 메서드를 많이 사용합니다.

void SomethingHappened(object sender, EventArgs ea)
{
   MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
   InvokeRequired ? Invoke( del ) : del(); 
}

이 주제에 대해서는 조금 늦었지만 다음 내용을 살펴보는 것이 좋습니다. 이벤트 기반 비동기 패턴.올바르게 구현되면 항상 UI 스레드에서 이벤트가 발생하는 것을 보장합니다.

다음은 하나의 동시 호출만 허용하는 간단한 예입니다.다중 호출/이벤트를 지원하려면 약간 더 많은 배관이 필요합니다.

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class MainForm : Form
    {
        private TypeWithAsync _type;

        [STAThread()]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new MainForm());
        }

        public MainForm()
        {
            _type = new TypeWithAsync();
            _type.DoSomethingCompleted += DoSomethingCompleted;

            var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill };

            var btn = new Button() { Text = "Synchronous" };
            btn.Click += SyncClick;
            panel.Controls.Add(btn);

            btn = new Button { Text = "Asynchronous" };
            btn.Click += AsyncClick;
            panel.Controls.Add(btn);

            Controls.Add(panel);
        }

        private void SyncClick(object sender, EventArgs e)
        {
            int value = _type.DoSomething();
            MessageBox.Show(string.Format("DoSomething() returned {0}.", value));
        }

        private void AsyncClick(object sender, EventArgs e)
        {
            _type.DoSomethingAsync();
        }

        private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e)
        {
            MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value));
        }
    }

    class TypeWithAsync
    {
        private AsyncOperation _operation;

        // synchronous version of method
        public int DoSomething()
        {
            Thread.Sleep(5000);
            return 27;
        }

        // async version of method
        public void DoSomethingAsync()
        {
            if (_operation != null)
            {
                throw new InvalidOperationException("An async operation is already running.");
            }

            _operation = AsyncOperationManager.CreateOperation(null);
            ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore);
        }

        // wrapper used by async method to call sync version of method, matches WaitCallback so it
        // can be queued by the thread pool
        private void DoSomethingAsyncCore(object state)
        {
            int returnValue = DoSomething();
            var e = new DoSomethingCompletedEventArgs(returnValue);
            _operation.PostOperationCompleted(RaiseDoSomethingCompleted, e);
        }

        // wrapper used so async method can raise the event; matches SendOrPostCallback
        private void RaiseDoSomethingCompleted(object args)
        {
            OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args);
        }

        private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e)
        {
            var handler = DoSomethingCompleted;

            if (handler != null) { handler(this, e); }
        }

        public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted;
    }

    public class DoSomethingCompletedEventArgs : EventArgs
    {
        private int _value;

        public DoSomethingCompletedEventArgs(int value)
            : base()
        {
            _value = value;
        }

        public int Value
        {
            get { return _value; }
        }
    }
}

다음과 같이 lazy programmer, 나는 이것을 하는 매우 게으른 방법을 가지고 있습니다.

내가 하는 일은 바로 이것이다.

private void DoInvoke(MethodInvoker del) {
    if (InvokeRequired) {
        Invoke(del);
    } else {
        del();
    }
}
//example of how to call it
private void tUpdateLabel(ToolStripStatusLabel lbl, String val) {
    DoInvoke(delegate { lbl.Text = val; });
}

DoInvoke를 함수 내부에 인라인하거나 별도의 함수 내에 숨겨 더러운 작업을 수행할 수 있습니다.

DoInvoke 메서드에 직접 함수를 전달할 수 있다는 점을 기억하세요.

private void directPass() {
    DoInvoke(this.directInvoke);
}
private void directInvoke() {
    textLabel.Text = "Directly passed.";
}

많은 간단한 경우에는 MethodInvoker 대리자를 사용하고 고유한 대리자 유형을 만들 필요가 없습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top