[英]Wait for thread to finish without blocking the UI thread
我试图统一创建一个消息框类,并且希望它与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[]
话,它的行为就会很怪异WaitHandle[] waits = new WaitHandle[2];
。
现在,它的工作方式更像我需要的,因为它不断在控制台中编写内容,而不是像以前的方法那样一次只发布21条消息,但是它运行的那一刻暂停了统一场景(我可以手动恢复它,并且程序将只运行很好),并继续在控制台中打印内容,并且出现此错误
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( )(在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()
第一行听起来像我在这里可能需要一个回调函数,所以我快速添加了一些东西只是为了对其进行测试
IAsyncResult r1 = ac1.BeginInvoke(ar => Debug.Log("Done"), null);
但是,没有运气,一切都没有改变。
有什么技巧可以解决整个问题(制作一个消息框,阻塞线程直到按下按钮),或者有关Microsoft如何在Windows窗体中实现该问题的更多信息?
停止尝试使用线程和其他.NET异步概念,并以希望使用的方式使用Unity。 创建一个自定义CustomYieldInstruction
,以查看是否显示了弹出窗口。
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.
}
您可以通过StartCoroutine
启动DialogExample()
或从另一个协程对其执行yield retrun
重新运行。
WinForms和Unity之间有很大的区别。 在WinForms中,您有一个UI线程,可能被模式窗体阻止。 在Unity中,您有多个具有多种方法的对象,其中脚本的执行顺序和某些引擎机制决定了如何在每个帧中执行它们。
但是,如果要在Unity中使用模式消息框,则可以通过向其添加布尔检查或禁用脚本来简单地阻止执行特定脚本的Update或FixedUpdate。 第一种方法提供了更多选择,但第二种方法则更容易。 但是请注意,禁用脚本会停止脚本中除Invoke和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是您要阻止的脚本的引用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.