System.Threading.Tasks.TaskCanceledException: 'A task was canceled.' when Closing App

I have a thread that change the field

private void SongChange(object sender, RoutedEventArgs e)
    {
        SongChangeAction();
        songLength = (int)BGMPlayer.NaturalDuration.TimeSpan.TotalSeconds;
        songProgress = new Thread(SongProgressUpdate) { IsBackground = true };
        songProgress.Start();
    }

    private void SongProgressUpdate()
    {
        while (true)
        {

            Dispatcher.Invoke(() => { workingResources.SongProgress = BGMPlayer.Position.TotalSeconds / songLength * 100; });
            Thread.Sleep(1000);
        }
    }

Note: songLength is a double, BGMPlayer is a MediaElement to play music in background, workingResources.SongProgress is a double bind to a Progress Bar

When I close the Program using "X" button or on taskbar, The program threw exception System.Threading.Tasks.TaskCanceledException Even I try to set the thread IsBackground = true but it work not throw exception If I stop the program using Visual Studio. The Program Throw exception at line:

Dispatcher.Invoke(() => { workingResources.SongProgress = BGMPlayer.Position.TotalSeconds / songLength * 100; });

I can prevent it by using try/catch but I want to find something more efficient

Upvotes: 1

Views: 5261

Answers (2)

DoronG
DoronG

Reputation: 2663

Try using a CancellationToken on close of the window:

private readonly CancellationTokenSource _shutDown = new CancellationTokenSource();

public WindowName()
{
    this.Closed =+ (s, e) => this._shutDown.Cancel();
}

private void SongChange(object sender, RoutedEventArgs e)
{
    SongChangeAction();
    songLength = (int)BGMPlayer.NaturalDuration.TimeSpan.TotalSeconds;
    songProgress = Task.Factory.StartNew(SongProgressUpdate, this._shutDown.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}

private void SongProgressUpdate()
{
    while (!this._shutDown.IsCancellationRequested)
    {
        Dispatcher.Invoke(() => { workingResources.SongProgress = BGMPlayer.Position.TotalSeconds / songLength * 100; });
        Thread.Sleep(1000);
    }
}

Upvotes: 2

Enigmativity
Enigmativity

Reputation: 117064

Try using Microsoft's Reactive Framework. Then you can do this:

private void SongChange(object sender, RoutedEventArgs e)
{
    SongChangeAction();
    songLength = (int)BGMPlayer.NaturalDuration.TimeSpan.TotalSeconds;
    IDisposable subscription =
        Observable
            .Interval(TimeSpan.FromSeconds(1.0))
            .ObserveOnDispatcher()
            .Subscribe(x => workingResources.SongProgress = BGMPlayer.Position.TotalSeconds / songLength * 100);
}

You just need to make sure that you save a reference to subscription and call .Dispose() on it when you are closing your app.

NuGet "System.Reactive" and "System.Reactive.Windows.Threading", and add using System.Reactive.Linq; to your code.

Upvotes: 1

Related Questions