That's it, I was entrusted with a multi-threaded large C # application. For this, I decided to use async / await . I am well aware of the use of IProgress<T> to report progress in the user interface (lets call it โpushingโ information into the user interface), but I also need to โpullโ the data from the user interface (in my case, this is a SpreadsheetGear workbook that contains data). This is a two-way interaction that I want to advise about ...
I am currently running a click event to start processing, and the code has the following structure:
CancellationTokenSource cancelSource; private async void SomeButton_Click(object sender, EventArgs e) { // Set up progress reporting. IProgress<CostEngine.ProgressInfo> progressIndicator = new Progress<CostEngine.ProgressInfo>(); // Set up cancellation support, and UI scheduler. cancelSource = new CancellationTokenSource(); CancellationToken token = cancelSource.Token; TaskScheduler UIScheduler = TaskScheduler.FromCurrentSynchronizationContext(); // Run the script processor async. CostEngine.ScriptProcessor script = new CostEngine.ScriptProcessor(this); await script.ProcessScriptAsync(doc, progressIndicator, token, UIScheduler); // Do stuff in continuation... ... }
Then in ProcessScriptAsync I have the following:
public async Task ProcessScriptAsync( SpreadsheetGear.Windows.Forms.WorkbookView workbookView, IProgress<ProgressInfo> progressInfo, CancellationToken token, TaskScheduler UIScheduler) { // This is still on the UI thread. // Here do some checks on the script workbook on the UI thread. try { workbookView.GetLock(); // Now perform tests... } finally { workbookView.ReleaseLock(); } // Set the main processor off on a background thread-pool thread using await. Task<bool> generateStageTask = null; generateStageTask = Task.Factory.StartNew<bool>(() => GenerateStage(workbookView, progressInfo, token, UIScheduler)); bool bGenerationSuccess = await generateStageTask; // Automatic continuation back on UI thread. if (!bGenerationSuccess) { // Do stuff... } else { // Do other stuff } }
That seems fine. The problem I currently have is the GenerateStage method, which now runs in the background thread of the thread stream
private bool GenerateStage( SpreadsheetGear.WorkbookView workbookView, IProgress<ProgressInfo> progressInfo, CancellationToken token, TaskScheduler scheduler) { ... // Get the required data using the relevant synchronisation context. SpreadsheetGear.IWorksheet worksheet = null; SpreadsheetGear.IRange range = null; Task task = Task.Factory.StartNew(() => { worksheet = workbookView.ActiveWorksheet; range = worksheet.UsedRange; }, CancellationToken.None, TaskCreationOptions.None, scheduler); try { task.Wait(); } finally { task.Dispose(); } // Now perform operations with 'worksheet'/'range' on the thread-pool thread... }
In this method, I need to extract data from the user interface and write data to the user interface many times. For writing, I can clearly use "progressInfo", but how to handle pulling information from the user interface. Here I used the UI thread synchronization context, but this will be done many times. Is there a better way to perform these operations / are there any flaws in my current approach?
Note. Obviously, I would return the Task.Factory.StartNew(...) code to the reusable method above is explicitly shown for security.