How to add CancellationToken on Unity Web request? - c#

I wrote the following code that successfully downloads a photo from the link. But I want to set a cancellationToken for the request that can be activated by pressing the x key. please guide me.
private async void GetImage()
{
var request = UnityWebRequest.Get(requestURI);
await RequestEnd(request);
// add cancellation when I press any key..
var date = request.downloadHandler.data;
texture2D = new Texture2D(1, 1);
texture2D.LoadImage(date);
}
private static async Task RequestEnd(UnityWebRequest request)
{
request.SendWebRequest();
Debug.Log("Request Send");
while (!request.isDone) await Task.Yield();
}

You need a variable of type CancellationTokenSource.
//inside the class
private CancellationTokenSource downloadCancellationTokenSource;
// inside GetImage.cs
this.downloadCancellationTokenSource = new CancellationTokenSource();
var token = this.downloadCancellationTokenSource.Token;
await RequestEnd(request, token);
And you add the token as a parameter to method RequestEnd():
private static async Task RequestEnd(UnityWebRequest request, CancellationToken token)
{
request.SendWebRequest();
Debug.Log("Request Send");
while (!request.isDone)
{
if (token.IsCancellationRequested)
{
Debug.Log("Task {0} cancelled");
token.ThrowIfCancellationRequested();
}
await Task.Yield();
}
}
Now, in an Update() method you can check for input "X" and cancel the async task:
private void Update()
{
if(Input.GetKeyDown("X")
{
if (this.downloadCancellationTokenSource != null)
{
this.downloadCancellationTokenSource.Cancel();
this.downloadCancellationTokenSource.Dispose();
this.downloadCancellationTokenSource = null;
}
}
}

Related

Why is my loop ending after task cancellation?

I have a music queue manager that plays the next music if the queue is not empty. I also have a "skip" function that stops the current music by cancelling the current task. To stop the music, I create and forward a CancellationTokenSource.Token to PlayMusicAsync() function. When the task "PlayMusicAsync()" is cancelled, the CancellationTokenSource is reseted and the loop ends, instead of play the next song.
MusicQueueManager.cs:
public class MusicQueueManager
{
private CancellationTokenSource _cts = new();
private readonly MusicPlayer _musicPlayer;
public MusicQueueManager(MusicPlayer musicPlayer)
{
_musicPlayer = musicPlayer;
}
public List<YoutubeMediaInfo> MusicQueue { get; private set; } = new();
public bool IsPlaying { get; private set; } = false;
public bool HasNextSong => MusicQueue.Count > 0;
public async Task StartPlayingAsync(IAudioClient audioClient, SocketCommandContext context)
{
IsPlaying = true;
while (HasNextSong)
{
var nextSong = PopSong();
if (nextSong is null)
{
await context.Message.ReplyAsync("Error.");
continue;
}
try
{
await _musicPlayer.PlayMusicAsync(nextSong, audioClient, context, _cts)
}
finally
{
_cts = new CancellationTokenSource();
}
await Task.Delay(1000);
}
IsPlaying = false;
}
public void SkipSong()
{
_cts.Cancel();
}
}
MusicPlayer.cs:
public class MusicPlayer
{
public async Task PlayMusicAsync(YoutubeMediaInfo youtubeMediaInfo, IAudioClient audioClient, SocketCommandContext context, CancellationTokenSource cts)
{
var youtube = new YoutubeClient();
var streamInfo = youtubeMediaInfo.StreamManifest?.GetAudioOnlyStreams().GetWithHighestBitrate();
if (streamInfo is null)
{
return;
}
var stream = await youtube.Videos.Streams.GetAsync(streamInfo);
var memoryStream = new MemoryStream();
await Cli.Wrap("ffmpeg")
.WithArguments(" -hide_banner -loglevel panic -i pipe:0 -ac 2 -f s16le -ar 48000 pipe:1")
.WithStandardInputPipe(PipeSource.FromStream(stream))
.WithStandardOutputPipe(PipeTarget.ToStream(memoryStream))
.ExecuteAsync();
using var discord = audioClient.CreatePCMStream(AudioApplication.Mixed);
try
{
await discord.WriteAsync(memoryStream.ToArray().AsMemory(0, (int)memoryStream.Length), cts.Token);
}
finally
{
await discord.FlushAsync();
}
}
}
I tried a solution that adds .ContinueWith(x => { return; }) to PlayMusicAsync() and it worked well, but I don't know why.
Code Snippet:
public async Task StartPlayingAsync(IAudioClient audioClient, SocketCommandContext context)
{
IsPlaying = true;
while (HasNextSong)
{
var nextSong = PopSong();
if (nextSong is null)
{
await context.Message.ReplyAsync("Erro.");
continue;
}
try
{
await _musicPlayer.PlayMusicAsync(nextSong, audioClient, context, _cts).ContinueWith(x => { return; });
}
finally
{
_cts = new CancellationTokenSource();
}
await Task.Delay(1000);
}
IsPlaying = false;
}
Upon cancellation it's most likely that await _musicPlayer.PlayMusicAsync throws the whole while loop "stops" (StartPlayingAsync exits with an exception).
In order to keep the loop going you need to catch OperationCanceledException:
try
{
await _musicPlayer.PlayMusicAsync(nextSong, audioClient, context,
}
catch (OperationCanceledException ex)
{
// Reste cts here
}
BTW. There is no need for PlayMusicAsync to accept a token source, and normally a method like this would accept just a token.

Cleaning up a Task in an AsyncDisposable (VSTHRD003)

I have a class which creates a Task which runs during its whole lifetime, that is, until Dispose() is called on it:
In the constructor I call:
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
The way I currently do it (which I am also not sure is the right way) is cancelling the CancellationToken, and waiting on the task.
public void Dispose()
{
if (_isDisposed)
{
return;
}
_workerCancellation.Cancel();
_worker.GetAwaiter().GetResult();
_isDisposed = true;
}
When I do the same in the AsyncDispose method like so:
public async ValueTask DisposeAsync()
{
await _worker;
}
I get this warning
How do I correctly dispose of such a worker? Thanks!
As requested, here is the full code of what I am trying to do:
public sealed class ActiveObjectWrapper<T, TS> : IAsyncDisposable
{
private bool _isDisposed = false;
private const int DefaultQueueCapacity = 1024;
private readonly Task _worker;
private readonly CancellationTokenSource _workerCancellation;
private readonly Channel<(T, TaskCompletionSource<TS>)> _taskQueue;
private readonly Func<T, TS> _onReceive;
public ActiveObjectWrapper(Func<T, TS> onReceive, int? queueCapacity = null)
{
_onReceive = onReceive;
_taskQueue = Channel.CreateBounded<(T, TaskCompletionSource<TS>)>(queueCapacity ?? DefaultQueueCapacity);
_workerCancellation = new CancellationTokenSource();
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
}
private async Task ProcessQueueAsync(CancellationToken cancellationToken)
{
await foreach (var (value, taskCompletionSource) in _taskQueue.Reader.ReadAllAsync(cancellationToken))
{
try
{
var result = _onReceive(value); // todo: do I need to propagate the cancellation token?
taskCompletionSource.SetResult(result);
}
catch (Exception exception)
{
taskCompletionSource.SetException(exception);
}
}
}
public async Task<TS> EnqueueAsync(T value)
{
// see: https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
var completionSource = new TaskCompletionSource<TS>(TaskCreationOptions.RunContinuationsAsynchronously);
await _taskQueue.Writer.WriteAsync((value, completionSource));
return await completionSource.Task;
}
public async ValueTask DisposeAsync()
{
if (_isDisposed)
{
return;
}
_taskQueue.Writer.Complete();
_workerCancellation.Cancel();
await _worker;
_isDisposed = true;
}
}
This is the pattern that I use when implementing both IDisposabe and IDisposeableAsync. It isn't strictly compliant with the .Net recommendations. I found that implementing DisposeAsyncCore() was unnecessary as my classes are sealed.
public void Dispose()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
Worker.GetAwaiter().GetResult();
}
public void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
if (disposing)
{
lock (isDisposing)
{
if (isDisposed)
{
return;
}
Cts.Cancel();
Cts.Dispose();
isDisposed = true;
}
}
}
public async ValueTask DisposeAsync()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
await Worker;
}
This looks like an attempt to build a TransformBlock<TIn,TOut> on top of Channels. Using a Channel properly wouldn't generate such a warning. There's no reason to use a single processing task, or store it in a field.
Using Blocks
First, the equivalent code using a TransformBlock<Tin,TOut> would be:
var block=new TransformBlock<TIn,TOut>(msgIn=>func(msgIn));
foreach(....)
{
block.Post(someMessage);
}
To stop it, block.Complete() would be enough. Any pending messages would still be processed. A TransformBlock is meant to forward its output to other blocks, eg an ActionBlock or BufferBlock.
var finalBlock=new ActionBlock<TOut>(msgOut=>Console.WriteLine(msgOut));
block.LinkTo(finalBlock,new DataflowLinkOptions{PropagateCompletion = true});
...
block.Complete();
await finalBlock.Completion;
Using Channels
Doing something similar with channels doesn't need explicit classes, or Enqueue/Dequeue methods. Channels are build with separate ChannelReader, ChannelWriter interfaces to make it easier to control ownership, concurrency and completion.
A similar pipeline using channels would require only some methods:
ChannelReader<string> FolderToChannel(string path,CancellationToken token=default)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
foreach(var path in Directory.EnumerateFiles(path))
{
await _writer.SendAsync(path);
if (token.CancellationRequested)
{
return;
}
}
},token).ContinueWith(t=>_writer.TryComplete(t.Exception));
return channel;
}
This produces a reader that can be passed to a processing method. One that could generate another reader with the results:
static ChannelReader<MyClass> ParseFile(this ChannelReader<string> reader,CancellationToken token)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
await foreach(var path in reader.ReadAllAsync(token))
{
var json= await File.ReadAllTextAsync(path);
var dto= JsonSerializer.DeserializeObject<MyClass>(json);
await _writer.SendAsync(dto);
}
},token).ContineWith(t=>writer.TryComplete(t.Exception);
return channel;
}
And a final step that only consumes a channel:
static async Task LogIt(this ChannelReader<MyClass> reader,CancellationToken token)
{
await Task.Run(async ()=>{
await foreach(var dto in reader.ReadAllAsync(token))
{
Console.WriteLine(dto);
}
},token);
}
The three steps can be combined very easily:
var cts=new CancellationTokenSource();
var complete=FolderToChannel(somePath,cts.Token)
.ParseFile(cts.Token)
.LogIt(cts.Token);
await complete;
By encapsulating the channel itself and the processing in a method there's no ambiguity about who owns the channel, who is responsible for completion or cancellation

Issue with CancellationTokenSource in C#

I am trying to create a helper class for sending some information periodically to backend server.
Attaching the code below.
public class HeartBeatService
{
private CancellationToken _cancellationToken;
private CancellationTokenSource _cancellationTokenSource;
public void StartHeartBeatService(TimeSpan timeSpan)
{
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
Task.Run(async () =>
{
while (!_cancellationToken.IsCancellationRequested)
{
SendHeartBeatToAzure();
try
{
await Task.Delay(timeSpan, _cancellationToken);
}
catch
{
break;
}
}
});
}
public void SuspendHeartBeatService()
{
_cancellationTokenSource?.Cancel();
}
private async void SendHeartBeatToAzure()
{
var platformService = ServiceLocator.Get<IPlatformService>();
var location = await platformService?.GetPositionAsync();
if (!double.IsNaN(location.Item1) && !double.IsNaN(location.Item2))
{
Debug.WriteLine($"Triggering Heartbeat with location{location.Item1},{location.Item2}");
//TODO Invoke heartbeat api call.
}
}
}
The code for sending the information to server is working fine.
But there is some issue with CancellationToken which is not working/it is not cancelling.
not sure what's wrong with the implementation.
Change the signature of the SendHeartBeatToAzure to return a Task, so that it can be awaited:
private async Task SendHeartBeatToAzure()
Then await the task returned by the method inside the loop. To achieve a stable and consisted heartbeat, it is a good idea to create the Task.Delay task before calling the method:
Task.Run(async () =>
{
while (true)
{
var delayTask = Task.Delay(timeSpan, _cancellationToken);
await SendHeartBeatToAzure();
await delayTask;
}
});
As a side note, you should probably store the task returned by Task.Run as a readonly property of the HeartBeatService class, so that the status of the task can be monitored.

Calling cancellationToken.Cancel() in Dispose of Controller?

I have controller, where I create a Cancellation Token in the constructor and use it in a long running await task (). Does disposing the Cancellation token in Controller.Dispose() causes the long running task to cancel?
public class SomeController : BaseInternalController
{
private CancellationTokenSource cancellationTokenSource;
public MyController()
{
cancellationTokenSource = new CancellationTokenSource();
}
public async Task<HttpResponseMessage> Post(SomeData data)
{
foreach (var item in data)
{
await longRunningTask(item, cancellationTokenSource.token);
}
}
protected override void Dispose(bool disposing)
{
if (cancellationTokenSource != null)
{
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
cancellationTokenSource = null;
}
base.Dispose(disposing);
}
}
Does disposing the Cancellation token in Controller.Dispose() causes
the long running task to cancel?
Depends on how your longRunningTask was implemented.
In this method you should explicitly check whether the cancellation is requested:
token.ThrowIfCancellationRequested();
After invoking of this method your task will be cancelled.
Cancellation example
If in the following example the ThrowIfCancellationRequested would not be invoked, the task would be run forever:
var cts = new CancellationTokenSource();
Task.Run(() =>
{
while (true)
cts.Token.ThrowIfCancellationRequested();
}, cts.Token);
You can learn more about cancellation here.
Note that after setting the cancellationTokenSource to null, you can get NullReferenceException in your foreach loop. I would suggest to copy your token into a local variable:
public async Task<HttpResponseMessage> Post(SomeData data)
{
var token = cancellationTokenSource.Token;
foreach (var item in data)
{
await longRunningTask(item, token);
}
}

A pattern to pause/resume an async task?

I have a mostly IO-bound continuous task (a background spellchecker talking to a spellcheck server). Sometimes, this task needs to be put on hold and resumed later, depending on the user activity.
While suspend/resume is essentially what async/await does, I've found little information on how to implement the actual pause/play logic for an asynchronous method. Is there a recommended pattern for this?
I've also looked at using Stephen Toub's AsyncManualResetEvent for this, but thought it might be an overkill.
Updated for 2019, I've recently had a chance to revisit this code, below is complete example as a console app (warning: PauseTokenSource needs good unit testing).
Note, in my case, the requirement was that when the consumer-side code (which requested the pause) would continue, the producer-side code should have already reached the paused state. Thus, by the time the UI is ready to reflect the paused state, all background activity is expected to have been already paused.
using System;
using System.Threading.Tasks;
using System.Threading;
namespace Console_19613444
{
class Program
{
// PauseTokenSource
public class PauseTokenSource
{
bool _paused = false;
bool _pauseRequested = false;
TaskCompletionSource<bool> _resumeRequestTcs;
TaskCompletionSource<bool> _pauseConfirmationTcs;
readonly SemaphoreSlim _stateAsyncLock = new SemaphoreSlim(1);
readonly SemaphoreSlim _pauseRequestAsyncLock = new SemaphoreSlim(1);
public PauseToken Token { get { return new PauseToken(this); } }
public async Task<bool> IsPaused(CancellationToken token = default(CancellationToken))
{
await _stateAsyncLock.WaitAsync(token);
try
{
return _paused;
}
finally
{
_stateAsyncLock.Release();
}
}
public async Task ResumeAsync(CancellationToken token = default(CancellationToken))
{
await _stateAsyncLock.WaitAsync(token);
try
{
if (!_paused)
{
return;
}
await _pauseRequestAsyncLock.WaitAsync(token);
try
{
var resumeRequestTcs = _resumeRequestTcs;
_paused = false;
_pauseRequested = false;
_resumeRequestTcs = null;
_pauseConfirmationTcs = null;
resumeRequestTcs.TrySetResult(true);
}
finally
{
_pauseRequestAsyncLock.Release();
}
}
finally
{
_stateAsyncLock.Release();
}
}
public async Task PauseAsync(CancellationToken token = default(CancellationToken))
{
await _stateAsyncLock.WaitAsync(token);
try
{
if (_paused)
{
return;
}
Task pauseConfirmationTask = null;
await _pauseRequestAsyncLock.WaitAsync(token);
try
{
_pauseRequested = true;
_resumeRequestTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_pauseConfirmationTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
pauseConfirmationTask = WaitForPauseConfirmationAsync(token);
}
finally
{
_pauseRequestAsyncLock.Release();
}
await pauseConfirmationTask;
_paused = true;
}
finally
{
_stateAsyncLock.Release();
}
}
private async Task WaitForResumeRequestAsync(CancellationToken token)
{
using (token.Register(() => _resumeRequestTcs.TrySetCanceled(), useSynchronizationContext: false))
{
await _resumeRequestTcs.Task;
}
}
private async Task WaitForPauseConfirmationAsync(CancellationToken token)
{
using (token.Register(() => _pauseConfirmationTcs.TrySetCanceled(), useSynchronizationContext: false))
{
await _pauseConfirmationTcs.Task;
}
}
internal async Task PauseIfRequestedAsync(CancellationToken token = default(CancellationToken))
{
Task resumeRequestTask = null;
await _pauseRequestAsyncLock.WaitAsync(token);
try
{
if (!_pauseRequested)
{
return;
}
resumeRequestTask = WaitForResumeRequestAsync(token);
_pauseConfirmationTcs.TrySetResult(true);
}
finally
{
_pauseRequestAsyncLock.Release();
}
await resumeRequestTask;
}
}
// PauseToken - consumer side
public struct PauseToken
{
readonly PauseTokenSource _source;
public PauseToken(PauseTokenSource source) { _source = source; }
public Task<bool> IsPaused() { return _source.IsPaused(); }
public Task PauseIfRequestedAsync(CancellationToken token = default(CancellationToken))
{
return _source.PauseIfRequestedAsync(token);
}
}
// Basic usage
public static async Task DoWorkAsync(PauseToken pause, CancellationToken token)
{
try
{
while (true)
{
token.ThrowIfCancellationRequested();
Console.WriteLine("Before await pause.PauseIfRequestedAsync()");
await pause.PauseIfRequestedAsync();
Console.WriteLine("After await pause.PauseIfRequestedAsync()");
await Task.Delay(1000);
}
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e);
throw;
}
}
static async Task Test(CancellationToken token)
{
var pts = new PauseTokenSource();
var task = DoWorkAsync(pts.Token, token);
while (true)
{
token.ThrowIfCancellationRequested();
Console.WriteLine("Press enter to pause...");
Console.ReadLine();
Console.WriteLine("Before pause requested");
await pts.PauseAsync();
Console.WriteLine("After pause requested, paused: " + await pts.IsPaused());
Console.WriteLine("Press enter to resume...");
Console.ReadLine();
Console.WriteLine("Before resume");
await pts.ResumeAsync();
Console.WriteLine("After resume");
}
}
static async Task Main()
{
await Test(CancellationToken.None);
}
}
}
AsyncManualResetEvent is exactly what you need, considering how messy your current code is. But a slightly better solution would be to use another approach from Stephen Toub: PauseToken. It works similarly to AsyncManualResetEvent, except its interface is made specifically for this purpose.
All the other answers seem either complicated or missing the mark when it comes to async/await programming by holding the thread which is CPU expensive and can lead to deadlocks. After lots of trial, error and many deadlocks, this finally worked for my high usage test.
var isWaiting = true;
while (isWaiting)
{
try
{
//A long delay is key here to prevent the task system from holding the thread.
//The cancellation token allows the work to resume with a notification
//from the CancellationTokenSource.
await Task.Delay(10000, cancellationToken);
}
catch (TaskCanceledException)
{
//Catch the cancellation and it turns into continuation
isWaiting = false;
}
}
it is works for me
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskTest2
{
class Program
{
static ManualResetEvent mre = new ManualResetEvent(false);
static void Main(string[] args)
{
mre.Set();
Task.Factory.StartNew(() =>
{
while (true)
{
Console.WriteLine("________________");
mre.WaitOne();
}
} );
Thread.Sleep(10000);
mre.Reset();
Console.WriteLine("Task Paused");
Thread.Sleep(10000);
Console.WriteLine("Task Will Resume After 1 Second");
Thread.Sleep(1000);
mre.Set();
Thread.Sleep(10000);
mre.Reset();
Console.WriteLine("Task Paused");
Console.Read();
}
}
}
Ok, maybe this deserves an answer, but I'm not so familiar with C# and I don't have MonoDevelop here, and it's 3 o' clock AM, so please have pity.
I'm suggesting something like this
class Spellchecker
{
private CancellationTokenSource mustStop = null;
private volatile Task currentTask = null;
//TODO add other state variables as needed
public void StartSpellchecker()
{
if (currentTask != null)
{
/*
* A task is already running,
* you can either throw an exception
* or silently return
*/
}
mustStop = new CancellationTokenSource();
currentTask = SpellcheckAsync(mustStop.Token);
currentTask.Start();
}
private async Task SpellcheckAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested))
{
/*
* TODO perform spell check
* This method must be the only one accessing
* the spellcheck-related state variables
*/
}
currentTask = null;
}
public async Task StopSpellchecker()
{
if (currentTask == null)
{
/*
* There is no task running
* you can either throw an exception
* or silently return
*/
}
else
{
/*
* A CancelAfter(TimeSpan) method
* is also available, which might interest you
*/
mustStop.Cancel();
//Remove the following lines if you don't want to wait for the task to actually stop
var task = currentTask;
if (task != null)
{
await task;
}
}
}
}

Categories

Resources