简体   繁体   中英

.net MAUI c# Background Task ContinueWith and notification Event

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!

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.

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.
...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM