Click here to Skip to main content
15,885,216 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
When I lived in the C++ world, I found the subject of threading intuitive. Here in C#, I find I get completely lost, because there seems to be so many ways to do things.

Task is probably the best choice most of the time but these are now so flexible that it is hard to know the best way.

I have what seems like a simple use case. I have an audio player that contains a function to fade the volume over a period of time. Because I don't want to block the calling code, I need the call to be fire-and-forget, so it seems like a case where it needs to do its thing on a separate thread. The problem is that the parent audio player can be disposed of while a volume fade is in progress.

I would be extremely grateful for any tips about how to best handle this.

The outline of the code could not be simpler:

public class AudioPlayer
{
	public void FadeVolume( double targetVolume, double fadeTimeSeconds )
	{
	}
}


What I have tried:

I've tried a menagerie of Task, await, Wait(), Task.Run(), and probably most of the rest of the available Task function calls, but I still don't seem to be able to find a way to handle this without some nasty exception or other.

Note that I do realise I may have to accept some kinds of exception (e.g. TaskCanceledException), which I aim to simply catch, maybe log, and then forget.

Mostly what I am concerned about is simply that I am handling things safely.
Posted
Updated 28-Oct-21 22:48pm
Comments
BillWoodruff 28-Oct-21 6:49am    
what version of .NET are you using ?
Patrick Skelton 28-Oct-21 6:54am    
Sorry, forgot that little detail. It's a Xamarin.Forms application. The platform-independent chunk of the code is .NETStandard2.0 but the target framework in the iOS project - where this audio player is implemented - is blank. I'm guessing the same?
BillWoodruff 28-Oct-21 7:14am    
This use scenario is so far outside my experience with threading i can't be helpful.

In .NET 5 and Core 3, you've now got: public void Kill (bool entireProcessTree);

See this about changes in usage of Thread.Abort:

https://blog.ndepend.com/on-replacing-thread-abort-in-net-6-net-5-and-net-core/

cheers, Bill

1 solution

How about something like this?
C#
public class AudioPlayer : IDisposable
{
    private CancellationTokenSource _tokenSource;
    
    private void CancelFade(CancellationTokenSource newTokenSource)
    {
        var oldTokenSource = Interlocked.Exchange(ref _tokenSource, newTokenSource);
        if (oldTokenSource != null)
        {
            oldTokenSource.Cancel();
            oldTokenSource.Dispose();
        }
    }
    
    public void Dispose()
    {
        CancelFade(null);
    }
    
    public Task FadeVolume(double targetVolume, double fadeTimeSeconds)
    {
        var newTokenSource = new CancellationTokenSource();
        CancelFade(newTokenSource);
        
        var fadeTime = TimeSpan.FromSeconds(fadeTimeSeconds);
        return FadeVolumeCore(targetVolume, fadeTime, newTokenSource.Token);
    }
    
    private async Task FadeVolumeCore(double targetVolume, TimeSpan fadeTime, CancellationToken cancellationToken)
    {
        var delay = TimeSpan.FromMilliseconds(100);
        int steps = (int)Math.Floor(fadeTime.TotalMilliseconds / delay.TotalMilliseconds);

        for (int i = 0; i < steps; i++)
        {
            if (cancellationToken.IsCancellationRequested) return;
            await Task.Delay(delay, cancellationToken);
            
            // Adjust the volume...
        }
    }
}
When the class is disposed, or the FadeVolume method is called again, any existing fade task will be cancelled.
 
Share this answer
 
Comments
Patrick Skelton 1-Nov-21 7:06am    
That is an excellent solution, sir! Thank you! (This is the first time I've passed a cancellation token into a Task.Delay(). I was surprised to get a TimeoutException. I'm sure there are practical reasons for this but semantically it seems a bit odd to me that I tell it to cancel and it responds with an error.)

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900