c# - 等待线程完成而不阻塞 UI 线程

标签 c# multithreading winforms unity3d messagebox

我正在尝试统一创建一个消息框类,我希望它的工作方式与 Windows 窗体中的消息框相同,后者等待按下按钮,然后执行代码。

        var mbox = MessageBox.Show("Test", "test", MessageBoxButtons.YesNo);
        var test = mbox == DialogResult.Cancel; <- it will wait here

我尝试用两种方式重现它

加入2个线程

    public void TestClick()
    {
        Thread thread1 = new Thread(TestMethod);
        thread1.Start();
        thread1.Join();
        Debug.Log("Done");
    }

    private void TestMethod()
    {
        float time = 0;
        while (time <= 20)
        {
            Thread.Sleep(100);
            time++;
            Debug.Log("Im doing heavy work");
        }
    }

这会阻塞主线程,只有在 TestMethod 完成后才会恢复,但我不希望这样,因为用户在那段时间无法与消息框进行交互。

异步方法

    public delegate int AsyncTask();

    public void TestClick()
    {
        RunAsyncAndWait();
        Debug.Log("Done");
    }

    public int Method1()
    {
        float time = 0;
        while (time <= 20)
        {
            Thread.Sleep(100);
            time++;
            Debug.Log("Im doing heavy work");
        }
        return 0;
    }


    public void RunAsyncAndWait()
    {
        AsyncTask ac1 = Method1;

        WaitHandle[] waits = new WaitHandle[1];
        IAsyncResult r1 = ac1.BeginInvoke(null, null);
        waits[0] = r1.AsyncWaitHandle;

        WaitHandle.WaitAll(waits);

    }

这与第一个完全一样,但如果我们将 WaitHandle[] 的大小更改为 WaitHandle[] waits = new WaitHandle[2];.

现在它的工作方式更像我需要的,因为它不断地在控制台中写入内容,而不是像以前的方法那样一次发布 21 条消息,但它运行时会暂停统一场景(我可以手动恢复它和程序将运行得很好)并且它继续在控制台中打印内容,我收到此错误

ArgumentNullException: null handle Parameter name: waitHandles System.Threading.WaitHandle.CheckArray (System.Threading.WaitHandle[] handles, Boolean waitAll) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Threading/WaitHandle.cs:77) System.Threading.WaitHandle.WaitAll (System.Threading.WaitHandle[] waitHandles) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Threading/WaitHandle.cs:109) Assets.Scripts.Test.RunAsyncAndWait () (at Assets/Scripts/Test.cs:40) Assets.Scripts.Test.TestClick () (at Assets/Scripts/Test.cs:16) UnityEngine.Events.InvokableCall.Invoke (System.Object[] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:153) UnityEngine.Events.InvokableCallList.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:630) UnityEngine.Events.UnityEventBase.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:765) UnityEngine.Events.UnityEvent.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:53) UnityEngine.UI.Button.Press () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:35) UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:44) UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:52) UnityEngine.EventSystems.ExecuteEvents.Execute[IPointerClickHandler] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction`1 functor) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:269) UnityEngine.EventSystems.EventSystem:Update()

第一行听起来好像我可能需要一个回调函数,所以我很快添加了一些东西来测试它

IAsyncResult r1 = ac1.BeginInvoke(ar => Debug.Log("Done"), null);

但运气不好,一切都没有改变。

关于我如何从整体上解决这个问题的任何提示(制作消息框,阻塞线程直到按下按钮),或者关于 Microsoft 如何在 Windows 窗体中实现它的更多信息?

最佳答案

WinForms 和 Unity 之间有很大的区别。在 WinForms 中,您有一个用于 UI 的线程,它可能会被模态表单阻塞。在 Unity 中,您有多个对象和多个方法,其中脚本执行顺序和一些引擎机制决定它们在每一帧中的执行方式。

但是,如果您希望在 Unity 中有一个模态消息框,您可以通过向其添加 bool 检查或禁用脚本来简单地阻止特定脚本的 Update 或 FixedUpdate 的执行。第一种方式提供更多选项,但第二种方式更容易。但是请注意,禁用脚本会停止其中的所有内容,InvokeCoroutine 除外。

您可以通过在底层对象上放置一个简单的 SpriteRenderer 或 Image 来阻止用户与底层对象的交互。此 mask 的透明度可以为零,应该是全屏大小并且必须打开Raycast Target

我更喜欢一个带有全屏 mask 的消息框,它有一个带有 alpha = .1 的简​​单黑色 Sprite

public GameObject ModalMessageBox;//game object of message box with a mask

public void TestClick()
{
    StartCoroutine(TestMethod);
    ModalMessageBox.setActive(true);
}

IEnumerator TestMethod()
{
    float time = 0;
    while (time <= 20)
    {
        yield return new WaitForSeconds(.1f);
        time++;
        Debug.Log("Im doing heavy work");
    }
    ModalMessageBox.setActive(false);
}

void Update()
{
    if(ModalMessageBox.activeSelf)
    {
        //handle message box
    }
    else
    {
        //handle normal update stuff
    }
}

请注意,所有其他脚本仍然会运行。如果您还必须阻止其他脚本的执行,那么您需要一个一个地执行。

注意:

由于禁用脚本不会停止它启动的协程,因此您不妨禁用脚本本身

public Script1 script1;
public Script2 script2;
public Script3 script3;

void BlockScripts(bool block)
{
    //for singleton scripts:
    Script1.Instance.enabled = !block;
    Script2.Instance.enabled = !block;
    Script3.Instance.enabled = !block;
    //for referenced scripts:
    script1.enabled = !block;
    script2.enabled = !block;
    script3.enabled = !block;

    //self
    enabled = !block;
}

public void TestClick()
{
    StartCoroutine(TestMethod);
    ModalMessageBox.setActive(true);

    BlockScripts(true);
}

IEnumerator TestMethod()
{
    float time = 0;
    while (time <= 20)
    {
        yield return new WaitForSeconds(.1f);
        time++;
        Debug.Log("Im doing heavy work");
    }

    ModalMessageBox.setActive(false);

    BlockScripts(false);
}

void Update()
{
}

其中 Script1、2、3 是单例类,script1、2、3 是您要阻止的脚本的引用。

关于c# - 等待线程完成而不阻塞 UI 线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41202166/

相关文章:

c# - 类库 - 程序不包含适合入口点的静态 ‘Main’ 方法

c# - 线程方法参数

c - 寻找轻量级跨平台C线程库

c# - 启动时出现 Visual Studio 调试器异常

c# - Control.Invalidate不会触发隐藏或不可见Control的绘制事件

c# - 我应该如何从我的服务层方法中公开总记录数和分页记录的 IEnumerable 集合?

c# - 使用 InkCanvas 中的笔触裁剪 BitmapImage

c# - 从不使用空值?

java - jstack 输出 - 获取 java Thread.getId 的 tid

c# - 修复拖放卡住资源管理器