如何在主窗体中等待在另一个线程中触发和完成的事件而不阻塞 UI

How to wait in main form for an event fired and completed in another thread without blocking the UI

我有一个关于一些代码的实际问题,我需要提供背景信息。

在我的 winform 项目中,我添加了一个引用 (Siemens.Sinumerik.Operate.Services.dll) 来设置、获取和监控 NC(数控)磨床的一些值。通过一些特定的值,我可以告诉机器做一些工作(移动研磨工具等)。机器完成工作后,它通过另一个值给我反馈。在我的 winform 项目中,我可以使用所谓的热链接监视这些事件。所以这意味着如果 NC 机器更改了值,则会在我的 winform 项目中触发一个事件。

我的目标是开始工作时设置一些值,然后等待机器的响应。

所以在我的主窗体中,我有一个按钮单击事件,它启动了 NC 机器的作业,它在我的主线程中运行。在我开始工作后,主线程需要等到热链接给我反馈并直到事件结束。问题是我必须在西门子的文档之后定义这个热链接。每个热链接都在不同的线程和自己的 class 中运行。现在我用 AutoResetEvent 阻塞了主线程和 UI,直到另一个线程 class 中的事件被触发并完成它的工作。问题是 UI 被阻止了,这是我不允许的。

所以我的问题是:我怎样才能在主线程中等待一个事件被触发并在另一个线程中完成并 class 而不会阻塞 UI?

主窗体中的按钮点击事件:

private static AutoResetEvent _waitHandle = new AutoResetEvent(false);

    // Button Click event that sets the values to cause NC machine to operate
    private void cmd_StartDauertest_Click(object sender, EventArgs e)
    {
        mySWE.SWETauschauftrag();

        _waitHandle.WaitOne();

        // more work will be done...
    }

class 称为 "Schnellwechseleinheit.cs",它具有当 NC 机器的值更改时触发的事件。 class 分为两部分。一半在 class 中执行,另一半在 frm main 中执行,但仍在 class "Schnellwechseleinheit.cs".

的线程中

前半段class里面"Schnellwechseleinheit.cs":

class Schnellwechseleinheit
{
    public delegate void HotlinkSWEHasChanged();
    public event HotlinkSWEHasChanged HotlinkSWEChanged;

    DataSvc svc_initSWEHotlink = null;

    Guid guid_initSWEHotlink;


    /// <summary>
    /// Creates the "hotlink for the machine"
    /// </summary>
    public void initSWEHotlink()
    {
        DataSvc svc_SWEInit = null;
        svc_SWEInit = new DataSvc();
        Item SWEInit = new Item(MTU_Settings.Default.SWE_ERGEBNIS);
        SWEInit.Value = 0;
        svc_SWEInit.Write(SWEInit);

        svc_initSWEHotlink = new DataSvc();
        Item itemSubscribe = new Item(MTU_Settings.Default.SWE_ERGEBNIS);

        guid_initSWEHotlink = svc_initSWEHotlink.Subscribe(OnInitSWEHotlinkChanged, itemSubscribe);
    }


    /// <summary>
    /// This is the event of the Hotlink. Is caused when the value of the NC machine changes
    /// </summary>
    /// <param name="guid"></param>
    /// <param name="item"></param>
    /// <param name="Status"></param>
    private void OnInitSWEHotlinkChanged(Guid guid, Item item, DataSvcStatus Status)
    {
        try
        {

            DataSvc svc_SWEErg = null;
            svc_SWEErg = new DataSvc();
            Item SWEErg = new Item(MTU_Settings.Default.SWE_ERGEBNIS);
            svc_SWEErg.Read(SWEErg);

            if (Convert.ToInt16(SWEErg.Value) == 0)
            {
                writeStatSWE("Reset PLC Variable AMR Ergebnis für Auftragsstart!");
            }
            else if (Convert.ToInt16(SWEErg.Value) == 1)
            {
                writeStatSWE("Transportauftrag SWE wurde erfolgreich abgeschlossen!");
            }
            else
            {
                writeStatSWE("Transportauftrag SWE wurde von PLC abgelehnt :::: Fehlercode :::: " + SWEErg.Value.ToString());
            }

            this.HotlinkSWEChanged();
        }

        catch (Exception ex)
        {
            writeStatSWE(ex.Message);
        }
    }
}

class "Schnellwechseleinheit.cs" 的后半部分 main.cs:

// Creating the object of the class "Schnellwechseleinheit" and adding the event
mySWE = new Schnellwechseleinheit();
        mySWE.initSWEHotlink();
        mySWE.HotlinkSWEChanged += mySWEHotlinkChanged;

/// <summary>
/// Second half of the hotlink (the event that is added)
/// </summary>
    private void mySWEHotlinkChanged()
    {

        if (mySWE.getSWEErg() == 1)
        {
            Werkzeug WZGetData = new Werkzeug();
            MagElements MagDataPocket1 = new MagElements();
            MagElements MagDataGreifer1 = new MagElements();
            MagElements MagDataGreifer2 = new MagElements();


            MagDataPocket1 = WZGetData.getWZData(21);
            if (MagDataPocket1 != null)
            {
                MagDataPocket1 = ToolsInMag[ToolsInMag.FindIndex(x => (x.TN == MagDataPocket1.TN && x.DN == MagDataPocket1.DN))];
                MagDataPocket1.ORT = myAMR.WriteORT(21);
                ToolsInMag[ToolsInMag.FindIndex(x => (x.TN == MagDataPocket1.TN && x.DN == MagDataPocket1.DN))] = MagDataPocket1;
            }

            MagDataGreifer1 = WZGetData.getWZData(10);
            if (MagDataGreifer1 != null)
            {
                MagDataGreifer1 = ToolsInMag[ToolsInMag.FindIndex(x => (x.TN == MagDataGreifer1.TN && x.DN == MagDataGreifer1.DN))];
                MagDataGreifer1.ORT = myAMR.WriteORT(10);
                ToolsInMag[ToolsInMag.FindIndex(x => (x.TN == MagDataGreifer1.TN && x.DN == MagDataGreifer1.DN))] = MagDataGreifer1;
            }

            MagDataGreifer2 = WZGetData.getWZData(11);
            if (MagDataGreifer2 != null)
            {
                MagDataGreifer2 = ToolsInMag[ToolsInMag.FindIndex(x => (x.TN == MagDataGreifer2.TN && x.DN == MagDataGreifer2.DN))];
                MagDataGreifer2.ORT = myAMR.WriteORT(11);
                ToolsInMag[ToolsInMag.FindIndex(x => (x.TN == MagDataGreifer2.TN && x.DN == MagDataGreifer2.DN))] = MagDataGreifer2;
            }

            _waitHandle.Set();

            UpdateMagDgv();

        }

一种方法是在 EventArgs 中有一个 SemaphoreSlim 属性 ,当事件处理程序完成时,它会发出信号,并由 UI.

例如,给定一个带有按钮 "Button1" 的默认 Windows 表单应用程序,首先像这样定义一个 EventArgs class:

public sealed class MyEventArgs : EventArgs
{
    public MyEventArgs(SemaphoreSlim finished)
    {
        Finished = finished;
    }

    public SemaphoreSlim Finished { get; }
}

表单定义了一个事件处理程序,如下所示:

public event EventHandler<MyEventArgs> SomeEvent;

按钮点击处理程序是:

async void Button1_Click(object sender, EventArgs e)
{
    var handler = SomeEvent;

    if (handler == null)
        return;

    Text = "Waiting for event to be handled.";
    button1.Enabled = false;

    using (var sem = new SemaphoreSlim(0, 1))
    {
        var args = new MyEventArgs(sem);
        handler(this, args);
        await sem.WaitAsync();
    }

    Text = "Finished waiting for event to be handled.";
    button1.Enabled = true;
}

然后可以像这样订阅和处理事件(来自程序实现):

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        using (var form = new Form1())
        {
            form.SomeEvent += onSomeEvent;
            Application.Run(form);
        }
    }

    static void onSomeEvent(object sender, MyEventArgs e)
    {
        Task.Run(() => handleEvent(e));
    }

    static void handleEvent(MyEventArgs e)
    {
        Thread.Sleep(4000);
        e.Finished.Release();
    }
}

注意处理程序如何启动新任务来处理事件,并向信号量发送信号以指示它何时完成。

如果你运行这个程序并点击按钮,标题会变成"Waiting for event to be handled."四秒,然后变成"Finished waiting for event to be handled.",

在此期间,UI 没有被阻塞,因为它正在等待信号量。


或者,如果处理事件的方法是同步的,您可以通过任务 运行 它并等待任务,而不需要信号量。

事件处理程序很简单:

public event EventHandler<EventArgs> SomeEvent;

按钮点击处理程序为:

async void Button1_Click(object sender, EventArgs e)
{
    var handler = SomeEvent;

    if (handler == null)
        return;

    Text = "Waiting for event to be handled.";
    button1.Enabled = false;

    await Task.Run(() => handler(this, EventArgs.Empty));

    Text = "Finished waiting for event to be handled.";
    button1.Enabled = true;
}

并且事件处理程序本身可以在 class 程序中实现,如下所示:

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

namespace WindowsFormsApp3
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            using (var form = new Form1())
            {
                form.SomeEvent += onSomeEvent;
                Application.Run(form);
            }
        }

        static void onSomeEvent(object sender, EventArgs e)
        {
            Thread.Sleep(4000);
        }
    }
}

肯定有多种方法可以在这里完成您想做的事情。

我第一个想到的是让你方法 private void cmd_StartDauertest_Click(object sender, EventArgs e) async

看起来像这样:private async void cmd_StartDauertest_Click(object sender, EventArgs e)

然后在您的方法中使用 await 关键字。您的方法主体看起来像这样:

await Task.Run(() => 
{
     mySWE.SWETauschauftrag();

    _waitHandle.WaitOne();
});