如何在没有窗体或控件的 Winform 应用程序中调用 UI 线程?

jac*_*078 1 .net c# multithreading winforms

我创建了一个托盘应用程序来控制一些硬件组件。如何在没有主窗体或控件的情况下调用 UI 线程?

托盘应用程序的启动方式为Application.Run(new MyTrayApp())

class MyTrayApp : ApplicationContext
{
    private NotifyIcon trayIcon;

    public MyTrayApp()
    {            
        trayIcon = new NotifyIcon()
        {
            Icon = Resources.app_icon,
            ContextMenu = new ContextMenu(new MenuItem[] {                
            new MenuItem("Exit", Exit)
        }),
            Visible = true
        };
        // context is still null here
        var context = SynchronizationContext.Current;
        // but I want to invoke UI thread in hardware events
        MyHardWareController controller= new MyHardWareController(context);
    }

    void Exit(object sender, EventArgs e)
    {
        // context is accessible here because this is a UI event
        // too late tho
        var context = SynchronizationContext.Current;
        trayIcon.Visible = false;
        Application.Exit();
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. Control.Invoke()不可用,因为没有控件
  2. 搜索建议SynchronizationContext.Current应保存以供以后调用,但没有ApplicationContext.Load()事件...?
  3. 我注意到这MainFormnull在整个周期中。SynchronizationContext我想知道在这种情况下如何初始化?

编辑:

只是为了添加一些关于我为什么要调用 UI 线程的背景信息。这是因为System.Threading.ThreadStateException当尝试访问 Windows 资源(例如ClipboardSendKeys在另一个线程中)时会抛出:

  HResult=0x80131520
  Message=Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.
  Source=System.Windows.Forms
  StackTrace:
...
Run Code Online (Sandbox Code Playgroud)

这是另一种蠕虫病毒,但仅供参考:

  1. [STAThreadAttribute]已设置为主功能(无效果)
  2. 创建新的 STA 线程将导致防病毒软件在编译时删除我的应用程序

因此Form.Invoke()或等效于调用主线程应该是最简单的。

编辑2:

添加用于重现错误的要点: https://gist.github.com/jki21/eb950df7b88c06cc5c6d46f105335bbf

jac*_*078 5

正如厌恶所提到的那样解决了它Application.Idle!谢谢大家的建议!

托盘应用程序:

class MyTrayApp: ApplicationContext {
  private MyHardwareController controller = null;

  public MyTrayApp() {
    Application.Idle += new EventHandler(this.OnApplicationIdle);
    // ...
  }

  private void OnApplicationIdle(object sender, EventArgs e) {
    // prevent duplicate initialization on each Idle event
    if (controller == null) {
      var context = TaskScheduler.FromCurrentSynchronizationContext();

      controller = new MyHardwareController((f) => {
        Task.Factory.StartNew(
          () => {
            f();
          },
          CancellationToken.None,
          TaskCreationOptions.None,
          context);
      });
    }
  }
  // ...
}
Run Code Online (Sandbox Code Playgroud)

我的硬件控制器:

class MyHardwareController {
  private Action < Action > UIInvoke;

  public MyHardwareController(Action < Action > UIInvokeRef) {
    UIInvoke = UIInvokeRef;
  }

  void hardware_Event(object sender, EventArgs e) {
    // Invoke UI thread
    UIInvoke(() => Clipboard.SetText("I am in UI thread!"));
  }

}

Run Code Online (Sandbox Code Playgroud)