WinForms 事件運行不正常。我想我明白為什么,但我很好奇解決它的最佳方法。
在下面的(人為的)代碼中,Load首先被呼叫,但它在await到達時放棄控制,然后Shown被呼叫。一旦Shown完成,Load終于可以完成了。
private async void SomeForm_Load(object sender, EventArgs e)
{
someLabel.Text = "Start Load";
await SomeMethod();
someLabel.Text = "Finish Load";
}
private void SomeForm_Shown(object sender, EventArgs e)
{
someLabel.Text = "Shown";
}
private async Task SomeMethod()
{
await Task.Delay(1).ConfigureAwait(false);
}
我需要確保Load完成后Shown才能執行。
這是我目前的解決方案:
Task _isLoadedTask;
private async void SomeForm_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource();
_isLoadedTask = tcs.Task;
someLabel.Text = "Start Load";
await SomeMethod();
someLabel.Text = "Finish Load";
tcs.TrySetResult();
}
private async void SomeForm_Shown(object sender, EventArgs e)
{
await _isLoadedTask;
someLabel.Text = "Shown";
}
這樣做的明顯缺點是我必須手動設定邊界/依賴項。確保事件按順序執行的最佳方法是什么?
uj5u.com熱心網友回復:
在這種情況下,覆寫OnLoad或訂閱Load具有相同的效果。
但是,訂閱Load會導致委托實體化和呼叫以及覆寫OnLoad不會并且會更好地控制將執行的內容和順序。
嘗試這個:
private Task isLoadedTask;
protected override void OnLoad(EventArgs e)
{
this.isLoadedTask = onl oadAsync();
base.OnLoad(e);
}
private async Task LoadAsync()
{
someLabel.Text = "Start Load";
await SomeMethod();
someLabel.Text = "Finish Load";
}
protected override async void OnShown(EventArgs e)
{
base.OnShown(e);
await _isLoadedTask;
someLabel.Text = "Shown";
}
uj5u.com熱心網友回復:
您正在以正確的順序獲取事件。將async void與await在事件處理程式是混亂的問題。從Form類的角度來看,當它呼叫您的Load事件處理程式時,事件處理程式方法會在 await inSomeMethod被命中時回傳。當SomeMethod任務完成時,Load 事件處理程式中的其余代碼作為UI 執行緒上的延續執行- 盡管它看起來仍然在 Load 事件處理程式中,因為那是您的代碼所在的位置,這不是什么編譯器發出。肯定有點混亂。Jon Skeet 的書C# in Depth:Fourth Edition可能包含了我所見過的最好的解釋之一。
此外,async void盡可能避免使用方法,因為拋出例外可能會使您的行程崩潰。為了解決這兩個問題,并增加一些清晰度,我將使用 Load 事件來啟動任務,然后在 Shown 中為其附加一個延續。這可確保在 Load 中的異步內容完成并顯示時要運行的代碼。
private Task _loadTask = Task.CompletedTask;
private Stopwatch _sw = Stopwatch.StartNew();
private void Form1_Load(object sender, EventArgs e)
{
Trace.TraceInformation("[{0}] {1} Form1_Load - Begin", Thread.CurrentThread.ManagedThreadId, _sw.ElapsedMilliseconds);
_loadTask = DoAsyncLoadStuff();
Trace.TraceInformation("[{0}] {1} Form1_Load - End", Thread.CurrentThread.ManagedThreadId, _sw.ElapsedMilliseconds);
}
private void Form1_Shown(object sender, EventArgs e)
{
Trace.TraceInformation("[{0}] {1} Form1_Shown - Begin", Thread.CurrentThread.ManagedThreadId, _sw.ElapsedMilliseconds);
_loadTask.ContinueWith(DoStuffAfterShownAndAsyncLoadStuff, null, TaskScheduler.FromCurrentSynchronizationContext());
Trace.TraceInformation("[{0}] {1} Form1_Shown - End", Thread.CurrentThread.ManagedThreadId, _sw.ElapsedMilliseconds);
}
private void DoStuffAfterShownAndAsyncLoadStuff(Task asyncLoadTask, object context)
{
Trace.TraceInformation("[{0}] {1} DoStuffAfterShownAndAsyncLoadStuff. Task was {2}", Thread.CurrentThread.ManagedThreadId,
_sw.ElapsedMilliseconds, asyncLoadTask.Status);
}
private async Task DoAsyncLoadStuff()
{
await Task.Delay(5000).ConfigureAwait(false);
Trace.TraceInformation("[{0}] {1} DoAsyncLoadStuff - Returning", Thread.CurrentThread.ManagedThreadId, _sw.ElapsedMilliseconds);
//throw new NotImplementedException();
}
上面的示例將向 Visual Studio 中的“輸出”視窗輸出執行緒 ID 和經過的毫秒數。調整延遲時間并取消注釋拋出例外。請注意,DoStuffAfterShownAndAsyncLoadStuff它將始終在 UI 執行緒上執行。
uj5u.com熱心網友回復:
我需要確保在 Shown 可以執行之前完成 Load。
你最好的開始選擇是它們async void之間的同步是困難的。
當您放棄"Finish Load"訊息時(無論如何,該訊息可見多長時間?)它變得非常易于管理:
Task loadingTask;
private void SomeForm_Load(object sender, EventArgs e) // not async
{
someLabel.Text = "Start Load";
loadingTask = SomeMethod();
someLabel.Text = "Load Started";
}
private async void SomeForm_Shown(object sender, EventArgs e)
{
await loadingTask;
someLabel.Text = "Shown";
}
private async Task SomeMethod()
{
await Task.Delay(1000); //.ConfigureAwait(false);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/348002.html
