我已經閱讀了許多關于 .NET 中多執行緒的以前的帖子,并嘗試了多種方法,但我正在努力使我的特定案例和 c#.NET 代碼能夠正常運行。我的代碼旨在在后臺運行長時間運行的行程,并將正在進行的進度報告到我的 Windows 表單 UI 中的日志顯示和同一表單上的進度條控制元件中。截至目前,它是可操作的,但 UI 執行緒正在鎖定,并且在后臺行程完成之前,訊息不會被推送到 UI。
為簡潔起見,我將在這里總結當前的框架......
用戶界面表單
在初始化期間,我們將同步背景關系設定如下...
// reference to synchronization context within the UI initialization
Form_StepProcessor.SynchronizationContext =
(WindowsFormsSynchronizationContext)System.Threading.SynchronizationContext.Current;
該表單還包括一個自定義事件處理程式,它將 Post 回呼包裝到同步背景關系...
// attach asynchronous result processing handler within the UI...
try
{
AbsNhdPlusDotNetControllerAsync.ProcessResult -= this.OnProcessDotNetAsyncResult;
}
catch
{ // ignore
}
finally
{
AbsNhdPlusDotNetControllerAsync.ProcessResult = this.OnProcessDotNetAsyncResult;
}
// process result event declared within the controller
public delegate void ProcessResultEvent(object sender, ProcessResultEventArgs e);
public static event ProcessResultEvent ProcessResult;
public class ProcessResultEventArgs : EventArgs
{
public Exception Exception { get; set; }
public enpls_ProcessStatus Status { get; set; } = enpls_ProcessStatus.Failure;
public string Message { get; set; } = string.Empty;
public int PercentComplete { get; set; } = 0;
}
// event handler within the UI to push messaging to log control and increment progress bar
private void OnProcessDotNetAsyncResult(object sender, AbsNhdPlusDotNetControllerAsync.ProcessResultEventArgs e)
{
Form_StepProcessor.SynchronizationContext.Post(new SendOrPostCallback(x =>
{
// DO STUFF (i.e. post messages to display, increment progress bar, etc.)...
}), e);
}
后臺執行緒
To launch the background process, we're using async-await calls leveraging .ConfigureAwait(false) which drill down to a controller "Execute" task method. This method includes several "InvokeMessage" calls which invoke the event to which the UI handler above is attached ...
// call internal to the controller which immediately calls the "Execute" task method
await this.Execute(pModel_Validation, sTask).ConfigureAwait(false);
protected override Task Execute(iNhdPlusBaseModel pModel, string sTask)
{
// set the synchronization context (this didn't seem to do anything)
SyncrhonizationContext.SetSynchronizationContext(Form_StepProcessor.SynchronizationContext);
// do stuff ...
// invoke log message ...
this.InvokeMessage("here's a sample log message!", 10);
// do stuff ...
// invoke log message ...
this.InvokeMessage("here's a second sample log message!", 20);
// etc.
return Task.CompletedTask;
}
public void InvokeMessage(string sMessage, int iPercentComplete)
{
var pDateTime = DateTime.Now.ToLocalTime();
var sHour = pDateTime.Hour.ToString("D2");
var sMinute = pDateTime.Minute.ToString("D2");
var sSecond = pDateTime.Second.ToString("D2");
var pArgs = new ProcessResultEventArgs
{
Status = enpls_ProcessStatus.Message,
Message = $"{sHour}:{sMinute}:{sSecond}->{sMessage}",
PercentComplete = iPercentComplete
};
AbsNhdPlusDotNetControllerAsync.ProcessResult?.Invoke(this, pArgs);
}
I'm at my wits end with this. The various practices underlying async-await and multi-threading implementations have drastically changed over the years, and are confusing. Unraveling different implementations to see what applies to my case has been where I'm struggling and feel that I'm coming up short. I'd very much appreciate any constructive help that can point me in the right direction for an approach that will prevent my UI from locking up and prevent my messaging from caching up without promptly being pushed to the UI.
uj5u.com熱心網友回復:
首先,.ConfigureAwait(false)避免在與配置呼叫之前相同的同步背景關系上對配置呼叫進行編組繼續。因此,您所做的描述和您發布的代碼顯然不匹配。
這是一個簡單的示例,可幫助您了解其作業原理。
Console.WriteLine($"Thread: {Thread.CurrentThead.ManagedThreadId}"); // Thread: X
await DoSomething();
Console.WriteLine($"Thread: {Thread.CurrentThead.ManagedThreadId}"); // Thread: X
現在與 .ConfigureAwait(false)
Console.WriteLine($"Thread: {Thread.CurrentThead.ManagedThreadId}"); // Thread: X
await DoSomething().ConfigureAwait(false);
Console.WriteLine($"Thread: {Thread.CurrentThead.ManagedThreadId}"); // Thread: Y
假設該方法DoSomething()執行一些異步操作,這將成立。
該.ConfigureAwait(false)對什么事情你里面沒有影響Execute()的方法,所以我認為你可以將其洗掉。
現在,awaiting something 不會讓它在單獨的執行緒上運行。你可能想要的更像是:
protected override void Execute(iNhdPlusBaseModel pModel, string sTask)
{
// do stuff ...
// invoke log message ...
this.InvokeMessage("here's a sample log message!", 10);
// do stuff ...
// invoke log message ...
this.InvokeMessage("here's a second sample log message!", 20);
// etc.
}
...
await Task.Factory.StartNew(() => Execute(pModel_Validation, sTask), TaskCreationOptions.LongRunning);
請注意,該方法Execute()不需要回傳 a Task,方法本身正在完成將指示Task創建者Task.Factory.StartNew()已完成。
另請注意,您不需要手動設定同步背景關系。在您的情況下,它只是一個空操作,因為Execute它與呼叫者在同一執行緒上運行,在我描述的情況下,它不能因為Execute在不同的執行緒上運行而完成。
如果無法更改Execute方法的原型,則可以執行以下操作:
protected override Task Execute(iNhdPlusBaseModel pModel, string sTask)
{
return Task.Factory.StartNew(() => {
// do stuff ...
// invoke log message ...
this.InvokeMessage("here's a sample log message!", 10);
// do stuff ...
// invoke log message ...
this.InvokeMessage("here's a second sample log message!", 20);
// etc.
}, TaskCreationOptions.LongRunning);
}
Also, to help you understand the SynchornizationContext, you can see it as a way for a thread to execute something requested by another thread. So if thread A wants thread B to run something (on thread B), the thread A have to get a SynchronizationContext instance of the thread B, and so thread A can push something in thread B's execution queue. Note that this holds true if B's SynchronizationContext does not delegate execution to another thread (e.g. ThreadPool) but run the pushed (Posted) job on itself (on its own thread).
One last thing, if you run a method returning a Task but that method only perform synchronous actions, the whole method will just run synchronously, and therefore, on the same thread.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/335991.html
