简体   繁体   English

等待线程完成而不阻塞UI线程

[英]Wait for thread to finish without blocking the UI thread

I'm trying to create a message box class in unity and I want it to work the same way as the message box in windows forms which waits for a button to be pressed and than executes the code after that. 我试图统一创建一个消息框类,并且希望它与Windows窗体中的消息框以相同的方式工作,该窗口等待一个按钮被按下,然后在此之后执行代码。

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

I've tried to recreate that in 2 ways 我尝试用两种方式重新创建

Joining 2 threads 正在加入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");
        }
    }

This one blocks the main thread and will resume only after TestMethod is completed but I don't want that because the user wont be able to interact with the message box during that time. 该线程阻塞了主线程,并且仅在TestMethod完成后才恢复,但是我不希望那样,因为在此期间用户将无法与消息框进行交互。

Asynchronous approach 异步方式

    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);

    }

This works exactly like the first one but it behaves weirdly if we change some stuff like the size of the WaitHandle[] to WaitHandle[] waits = new WaitHandle[2]; 这和第一个完全一样,但是如果我们将诸如WaitHandle[]的大小更改为WaitHandle[]话,它的行为就会很怪异WaitHandle[] waits = new WaitHandle[2]; .

Now this works more like what I need as it continuously writes stuff in the Console rather than just posting 21 messages at once like the previous methods but the moment it runs it pauses the unity scene (I can manually resume it and the program will run just fine) and it keeps on printing stuff in the console and I get this error 现在,它的工作方式更像我需要的,因为它不断在控制台中编写内容,而不是像以前的方法那样一次只发布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 ( ArgumentNullException:空句柄参数名称:waitHandles System.Threading.WaitHandle.CheckArray(System.Threading.WaitHandle []处理,布尔值waitAll)(位于/Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Threading /WaitHandle.cs:77)System.Threading.WaitHandle.WaitAll(System.Threading.WaitHandle [] waitHandles)(位于/Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Threading/WaitHandle.cs :109)Assets.Scripts.Test.RunAsyncAndWait()(在Assets / Scripts / Test.cs:40)Assets.Scripts.Test.TestClick()(在Assets / Scripts / Test.cs:16)UnityEngine.Events.InvokableCall .Invoke(System.Object []参数)(在C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:153)UnityEngine.Events.InvokableCallList.Invoke(System.Object []参数)(在C: /buildslave/unity/build/Runtime/Export/UnityEvent.cs:630)UnityEngine.Events.UnityEventBase.Invoke(System.Object []参数)(在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() )(在C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:53)UnityEngine.UI.Button.Press()(在C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/ UI / Core / Button.cs:35)UnityEngine.UI.Button.OnPointerClick(UnityEngine.EventSystems.PointerEventData eventData)(位于C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button。 cs:44)UnityEngine.EventSystems.ExecuteEvents.Execute(IPointerClickHandler处理程序,UnityEngine.EventSystems.BaseEventData eventData)(位于C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:52) .EventSystems.ExecuteEvents.Execute [IPointerClickHandler](UnityEngine.GameObject目标,UnityEngine.EventSystems.BaseEventData eventData,UnityEngine.EventSystems.EventFunction`1函数)(位于C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/ EventSystem / ExecuteEvents.cs:269)UnityEngine.EventSystems.EventSystem:Update()

The first line sounded to me like I might need a callback function here so I quickly added something just to test it out 第一行听起来像我在这里可能需要一个回调函数,所以我快速添加了一些东西只是为了对其进行测试

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

But with no luck nothing changed. 但是,没有运气,一切都没有改变。

Any tips how can I approach this problem as a whole (making a message box, blocking the thread until a button is pressed), or maybe some more info on how Microsoft has implemented that in windows forms ? 有什么技巧可以解决整个问题(制作一个消息框,阻塞线程直到按下按钮),或者有关Microsoft如何在Windows窗体中实现该问题的更多信息?

Stop trying to use threads and other .NET async concepts and use Unity the way it wants to be used. 停止尝试使用线程和其他.NET异步概念,并以希望使用的方式使用Unity。 Create a custom CustomYieldInstruction that sees if your popup window is shown. 创建一个自定义CustomYieldInstruction ,以查看是否显示了弹出窗口。

class WaitWhile: CustomYieldInstruction {
    Func<bool> m_Predicate;

    public override bool keepWaiting { get { return m_Predicate(); } }

    public WaitWhile(Func<bool> predicate) { m_Predicate = predicate; }
}

It is used like 它像

public GameObject window; //the window that will be shown.

IEnumerator DialogExample()
{
    window.SetActive(true);
    yield return new WaitWhile(() => window.activeInHierarchy);

    //Code here does not run till after the window is deactivated.
}

You start the DialogExample() via a StartCoroutine or performing a yield retrun on it from another coroutine. 您可以通过StartCoroutine启动DialogExample()或从另一个协程对其执行yield retrun重新运行。

There is a big difference between WinForms and Unity. WinForms和Unity之间有很大的区别。 In WinForms you have one thread for UI which could be blocked by a modal form. 在WinForms中,您有一个UI线程,可能被模式窗体阻止。 In Unity you have multiple objects with multiple methods where script execution order and some engine mechanisms decide how they should be executed in each frame. 在Unity中,您有多个具有多种方法的对象,其中脚本的执行顺序和某些引擎机制决定了如何在每个帧中执行它们。

However if you want to have a modal message box in Unity, you can simply block the execution of the Update or FixedUpdate of a specific script by adding a boolean check to it or by disabling the script. 但是,如果要在Unity中使用模式消息框,则可以通过向其添加布尔检查或禁用脚本来简单地阻止执行特定脚本的Update或FixedUpdate。 First way provides more options but second one is easier. 第一种方法提供了更多选择,但第二种方法则更容易。 However be aware that disabling a script stops everything in it except Invoke and Coroutine . 但是请注意,禁用脚本会停止脚本中除InvokeCoroutine之外的所有内容。

You can block user's interactions with underlying objects by putting a simple SpriteRenderer or Image over them. 您可以通过在对象上放置一个简单的SpriteRenderer或Image来阻止用户与它们的交互。 This mask can have zero transparency, should be full screen size and must have Raycast Target toggled on. 蒙版的透明度可以为零,应为全屏尺寸,并且必须启用“ Raycast Target ”。

I would prefer a message box with a fullscreen mask behind it which has a simple black sprite with alpha = .1 我希望有一个带有全屏蒙版的消息框,它后面有一个带有alpha = .1的简单黑色精灵

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
    }
}

Note that all other scripts will run nevertheless. 请注意,所有其他脚本仍将运行。 If you have to block the execution of other scripts as well, then you need to do it one by one. 如果还必须阻止其他脚本的执行,则需要一个接一个地执行。

Note: 注意:

Since disabling a script does not stop the coroutines it started, you might as well disable the script itself 由于禁用脚本不会停止其启动的协程,因此您最好禁用脚本本身

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()
{
}

where Script1,2,3 are singleton classes and script1,2,3 are references of scripts you want to block. 其中Script1,2,3是单例类,而script1,2,3是您要阻止的脚本的引用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM