I've read many previous posts on multithreading in .NET, and tried a number of approaches but I'm struggling to get my specific case and c#.NET code to behave. My code is designed to run long-running processes in the background and report ongoing progress into a log display in my Windows Forms UI and to a progress bar control on the same form. As of right now it's operational, but the UI thread is locking up and the messages don't get pushed to the UI till the background process reaches completion.
For the sake of brevity, I'll summarize the current framework here...
User Interface Form
During initialization, we set the Synchronization context as follows ...
// reference to synchronization context within the UI initialization
Form_StepProcessor.SynchronizationContext =
(WindowsFormsSynchronizationContext)System.Threading.SynchronizationContext.Current;
The form also includes a custom event handler which wraps a Post callback to the syncrhonization context ...
// 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);
}
Background Thread
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.