在没有控制对象存在的UI线程上运行代码

Mar*_*ser 11 invoke ui-thread invokerequired winforms

我目前正在尝试编写一个组件,其中某些部分应该在UI线程上运行(解释会很长).所以最简单的方法是将控件传递给它,并在其上使用InvokeRequired/Invoke.但我不认为将控件引用传递给"数据/背景"组件是一个好设计,所以我正在寻找一种在UI线程上运行代码的方法,而无需提供控件.像WPF中的Application.Dispatcher.Invoke ...

任何想法,马丁

Jud*_*ngo 19

有一种更好,更抽象的方法可以在WinForms和WPF上运行:

System.Threading.SynchronizationContext.Current.Post(theMethod, state);
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为WindowsForms将WindowsFormsSynchronizationContext对象安装为当前同步上下文.WPF执行类似的操作,安装它自己的专用同步上下文(DispatcherSynchronizationContext).

.Post对应于control.BeginInvoke,.Send对应于control.Invoke.

  • 在UI线程上访问SyncrhonizationContext.Current.保存以供以后使用,当你在另一个线程上时. (2认同)

Dou*_*rch 2

你是对的,将控制权传递给线程是不好的。Winforms 控件是单线程的,将它们传递给多个线程可能会导致竞争条件或破坏您的 UI。相反,您应该使线程的功能可供 UI 使用,并在 UI 良好且准备就绪时让它调用线程。如果您想让后台线程触发 UI 更改,请公开后台事件并从 UI 订阅它。线程可以在任何需要的时候触发事件,并且 UI 可以在有能力时响应它们。

在线程之间创建这种不阻塞 UI 线程的双向通信需要大量工作。下面是一个使用 BackgroundWorker 类的高度缩写的示例:

public class MyBackgroundThread : BackgroundWorker
{
    public event EventHandler<ClassToPassToUI> IWantTheUIToDoSomething;

    public MyStatus TheUIWantsToKnowThis { get { whatever... } }

    public void TheUIWantsMeToDoSomething()
    {
        // Do something...
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        // This is called when the thread is started
        while (!CancellationPending)
        {
            // The UI will set IWantTheUIToDoSomething when it is ready to do things.
            if ((IWantTheUIToDoSomething != null) && IHaveUIData())
                IWantTheUIToDoSomething( this, new ClassToPassToUI(uiData) );
        }
    }
}


public partial class MyUIClass : Form
{
    MyBackgroundThread backgroundThread;

    delegate void ChangeUICallback(object sender, ClassToPassToUI uiData);

    ...

    public MyUIClass
    {
        backgroundThread = new MyBackgroundThread();

        // Do this when you're ready for requests from background threads:
        backgroundThread.IWantTheUIToDoSomething += new EventHandler<ClassToPassToUI>(SomeoneWantsToChangeTheUI);

        // This will run MyBackgroundThread.OnDoWork in a background thread:
        backgroundThread.RunWorkerAsync();
    }


    private void UserClickedAButtonOrSomething(object sender, EventArgs e)
    {
        // Really this should be done in the background thread,
        // it is here as an example of calling a background task from the UI.
        if (backgroundThread.TheUIWantsToKnowThis == MyStatus.ThreadIsInAStateToHandleUserRequests)
            backgroundThread.TheUIWantsMeToDoSomething();

        // The UI can change the UI as well, this will not need marshalling.
        SomeoneWantsToChangeTheUI( this, new ClassToPassToUI(localData) );
    }

    void SomeoneWantsToChangeTheUI(object sender, ClassToPassToUI uiData)
    {
        if (InvokeRequired)
        {
            // A background thread wants to change the UI.
            if (iAmInAStateWhereTheUICanBeChanged)
            {
                var callback = new ChangeUICallback(SomeoneWantsToChangeTheUI);
                Invoke(callback, new object[] { sender, uiData });
            }
        }
        else
        {
            // This is on the UI thread, either because it was called from the UI or was marshalled.
            ChangeTheUI(uiData)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)