等待线程完成而不阻塞 UI 线程
Wait for thread to finish without blocking the UI thread
我正在尝试统一创建一个消息框 class,我希望它的工作方式与 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 表单中实现该问题的更多信息?
停止尝试使用线程和其他 .NET 异步概念,按照它想要的方式使用 Unity。创建自定义 CustomYieldInstruction
以查看是否显示您的弹出窗口 window。
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 或图像来阻止用户与底层对象的交互。此 遮罩 的透明度可以为零,应该是全屏大小并且必须启用 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 是您要阻止的脚本的引用。
我正在尝试统一创建一个消息框 class,我希望它的工作方式与 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 表单中实现该问题的更多信息?
停止尝试使用线程和其他 .NET 异步概念,按照它想要的方式使用 Unity。创建自定义 CustomYieldInstruction
以查看是否显示您的弹出窗口 window。
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 或图像来阻止用户与底层对象的交互。此 遮罩 的透明度可以为零,应该是全屏大小并且必须启用 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 是您要阻止的脚本的引用。