[英]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.