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

时间:2016-12-17 19:15:46

标签: 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句柄   参数名称:waitHandles   System.Threading.WaitHandle.CheckArray(System.Threading.WaitHandle [] handle,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()(在Assets / Scripts / Test.cs:40)   Assets.Scripts.Test.TestClick()(在Assets / Scripts / Test.cs:16)   UnityEngine.Events.InvokableCall.Invoke(System.Object [] args)(在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()(在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 handler,UnityEngine.EventSystems.BaseEventData eventData)(在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)(在C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI /EventSystem/ExecuteEvents.cs:269)   UnityEngine.EventSystems.EventSystem:更新()

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

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

但没有运气没有改变。

任何提示我如何解决整个问题(制作一个消息框,在按下按钮之前阻止线程),或者可能还有一些关于Microsoft如何在Windows窗体中实现它的信息?

2 个答案:

答案 0 :(得分:3)

停止尝试使用线程和其他.NET异步概念,并按照它想要的方式使用Unity。创建一个自定义CustomYieldInstruction,查看是否显示了弹出窗口。

private void button1_Click(object sender, EventArgs e)
{
    DBConnect conn = new DBConnect();
    conn.Insert("rezervationinformations", 
    new string[] {"value1","value2","value3","value4","value5","value6"}) );
}

一样使用
class WaitWhile: CustomYieldInstruction {
    Func<bool> m_Predicate;

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

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

您可以通过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. } 启动DialogExample()或从其他协程开始StartCoroutine

答案 1 :(得分:3)

WinForms和Unity之间存在很大差异。在WinForms中,您有一个用于UI的线程,可以通过模式窗体阻止。在Unity中,您有多个具有多种方法的对象,其中脚本执行顺序和一些引擎机制决定了它们应该如何在每个帧中执行。

但是,如果要在Unity中使用模式消息框,可以通过向其添加布尔检查或禁用脚本来阻止特定脚本的Update或FixedUpdate的执行。第一种方式提供更多选择,但第二种方式更容易。但请注意,禁用脚本会停止其中的所有内容,但调用 Coroutine 除外。

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

我更喜欢一个带有全屏遮罩的消息框,后面有一个简单的黑色精灵,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
    }
}

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

注意:

由于禁用脚本并不会停止它启动的协同程序,因此您也可以禁用脚本本身

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是您要阻止的脚本的引用。