Home > Blockchain >  .net MAUI c# Background Task ContinueWith and notification Event
.net MAUI c# Background Task ContinueWith and notification Event

Time:08-19

this is a newbie-question.

I'm just digging in to c# and async and whyt i would like to have:

  • click Button
  • run several tasks in order but in background-thread, one after another
  • running tasks should notifiy their progress if possible

right now i can click the botton and start the task-chain, but within the completition event i would like (for testing) show a message-box every time a task has finished. this may lead to a crash (?) and i don't know why since i thought i would be within the ui-thread ...

here are some parts of the code:

AppViewModel:

    void handlePhaseCompletedEvent(object sender, SyncPhaseCompletedEventArgs e)
    {
        Shell.Current.DisplayAlert("TEST", "PHASE "   e.phase.ToString(), "OK"); // <<<< doesn't show up, maybe because its crashing a short time after?
        syncToolService.StartSyncPhaseAsync(e.phase   1, this); // <<<< seems to crash here?
    }

    [RelayCommand]
    async Task StartSyncAsync()
    {
        syncToolService.NotifySyncPhaseCompleted  = handlePhaseCompletedEvent;
        syncToolService.StartSyncPhaseAsync(0, this);
    }   

syncToolService:

public event EventHandler<SyncPhaseCompletedEventArgs> NotifySyncPhaseCompleted;

    public async Task StartSyncPhaseAsync(int phase, AppViewModel viewModel)
    {
        // search for Remote-peer
        if (phase == 0)
        {
            Task t = new Task(() => Task.Delay(100)); // dummy, not implemented yet
            t.ConfigureAwait(false);
            t.ContinueWith(t => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
            t.Start();
            return;
        }

        // Remote Sync start preparations
        if (phase == 1)
        {
            Task t = new Task(() => Task.Delay(100)); // dummy, not implemented yet
            t.ConfigureAwait(false);
            t.ContinueWith(t => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
            t.Start();
            return;
        }

        //////// LOCAL PREPARATIONS

        // read local files
        if (phase == 2)
        {
            Task t = new Task(() => BPMSyncToolService.loadLocalData(viewModel.DataFiles));
            t.ConfigureAwait(false);
            t.ContinueWith(t => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
            t.Start();
            return;
        }
    }

basicly i thought StartSyncPhaseAsync would run a Task (and it seems to do so) and it also seems to trigger the event (whicht seems not to raise the exeption) when running line by line in debug it crashes after syncToolService.StartSyncPhaseAsync(e.phase 1, this); with this stack:

>   [Exception] WinRT.Runtime.dll!WinRT.ExceptionHelpers.ThrowExceptionForHR.__Throw|20_0(int hr)   
    [Exception] Microsoft.WinUI.dll!Microsoft.UI.Xaml.Controls.ContentDialog._IContentDialogFactory.CreateInstance(object baseInterface, out System.IntPtr innerInterface)  
    [Exception] Microsoft.WinUI.dll!Microsoft.UI.Xaml.Controls.ContentDialog.ContentDialog()    
    [Exception] Microsoft.Maui.Controls.dll!Microsoft.Maui.Controls.Platform.AlertManager.AlertRequestHelper.OnAlertRequested(Microsoft.Maui.Controls.Page sender, Microsoft.Maui.Controls.Internals.AlertArguments arguments)  
    System.Private.CoreLib.dll!System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()   
    System.Private.CoreLib.dll!System.Threading.Tasks.Task.ThrowAsync.AnonymousMethod__128_1(object state)  
    System.Private.CoreLib.dll!System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()   
    System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()  
    System.Private.CoreLib.dll!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart() 

i also may have a general problem in my design, any help would be great!

CodePudding user response:

You can capture SynchronizationContext in your syncToolService in constructor, or by defining explicitly API for capturing, kinda:

public void CaptureSynchronizationContext(SynchronizationContext context)
{   
    var current = SynchronizationContext.Current;
    if (context is null)
    {            
        this.capturedScheduler = TaskScheduler.Current;
        return;
    } 
   SynchronizationContext.SetSynchronizationContext(context);
   this.capturedScheduler = TaskScheduler.FromCurrentSynchronizationContext();
   SynchronizationContext.SetSynchronizationContext(current);
}

Add make some wrapper for your logic to be called in specified context:

private void RunTaskWithContinuation(Task task, Action<Task> continuation)
{
  task.ConfigureAwait(false);
  task.ContinueWith(t => continuation(t), capturedScheduler);
  task.Start();
}

So, somewhere in your UI:

// afaik you should call it once per every Window
syncToolService.CaptureSynchronizationContext(SynchronizationContext.Current);

And your code above would look like this:

// read local files
if (phase == 2)
{
    Task t = new Task(() => BPMSyncToolService.loadLocalData(viewModel.DataFiles));
    RunTaskWithContinuation(t, () => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
}

Not tested, but i would try this idea first.
Btw, if SynchronizationContext is null, guess your problem would be persisted.

There is space for refactoring, just wanted to show the idea.

UPDATE

There is ReportProgress type - right tool for reports in multithreaded environment. May be this is what you are looking for.
But it works the same way, as i did above - via context capturing.

CodePudding user response:

Given async/await, it is almost never necessary to use task continuations or ConfigureAwait.

  • To start a sequence in the background, wrap the sequence in Task.Run.
  • To report progress on UI thread, use Dispatcher.Dispatch.

Example:

// IMPORTANT: `await`.
// Otherwise, current method would continue before Task.Run completes.
await Task.Run(async () =>
{
    // Now on background thread.
    ...

    // Report progress to UI.
    Dispatcher.Dispatch(() =>
    {
        // Code here is queued to run on MainThread.
        // Assuming you don't need to wait for the result,
        // don't need await/async here.
    }

    // Still on background thread.
    ...
};

// This is effectively the "continuation": Code here runs after Task.Run completes.
...

UPDATE

In response to comment, this is how you use async/await to start a sequence of tasks, without waiting for the result:

If your top-level code does UI calls:

// This queues an independent execution to MainThread.
// We don't "await" the Dispatch, because we want it to run independently.
Dispatcher.Dispatch(async () => await TopMethod());

If your top-level code does not do UI calls:

// This queues an independent execution to the Thread Pool.
// We don't "await" the Run, because we want it to run independently.
Task.Run(async () => await TopMethod());

In either case, instead of using continuations, TopMethod uses awaits to sequence the tasks:

async void TopMethod()
{
    await ..Task1..;
    await ..Task2..;
    await ..Task3..;
}

This is equivalent to Task1.ContinueWith(Task2.ContinueWith(Task3)); (Off the top of my head; I may not have the syntax quite right on this.)


If you are on a background thread (did Task.Run), then to do UI calls, simply wrap in Dispatcher.Dispatch( ... ). As shown in first code snippet.

  • Related