0

I have a Task inside a function, this is the whole Function:

public async Task CreateRoom(GameTypes game)
{
    // Get the user that called this function
    User CurrentUser = ConnectedUsers.Single(r => r.Id == Context.ConnectionId);

    // Set the user name to Player 1
    CurrentUser.Name = "Player 1";

    // Add the user to a new list of Users and add that list to the Room
    UsersInRoom = new List<User>();
    UsersInRoom.Add(CurrentUser);
    Room room = new Room() { RoomName = CurrentUser.Name, Game = game, UsersInRoom = UsersInRoom };
    AllRooms.Add(room);

    // Subscribe the user to the lobby
    await Groups.AddToGroupAsync(CurrentUser.Id, CurrentUser.Name);
    CurrentUser.Room = CurrentUser.Name;

    // Send to the user that the wait screen needs to be opened
    await Clients.Caller.SendAsync("OpenWaitScreen");

    // Send to all other users to update the lobby list.
    await Clients.Others.SendAsync("ForceRoomRequest", game);

    // If in 5 minutes no user joins the lobby than send to the caller NoUsersFound
    await Task.Delay(300000).ContinueWith(async task =>
    {
        await Clients.Caller.SendAsync("NoUsersFound");
        AllRooms.Remove(room);
    });
}

I have found some things on Stackoverflow but i dont know how to implement them.

But I want to be able to cancel this task in a other function.
How would I do that?

EDIT: This is the piece of javascript code that i want to rewrite to C#:

setTimeout(function (){
    socket.emit('NoUsersFound');
    delete AllRooms[data.room];
},  300000);
BlueDragon709
  • 196
  • 1
  • 12
  • 2
    https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/cancel-an-async-task-or-a-list-of-tasks – Leszek P Feb 21 '19 at 10:56
  • 2
    If you are using async/await, don't use `.ContinueWith` – FCin Feb 21 '19 at 10:57
  • 1
    Why @FCin? Seems a perfectly legitimate thing to do – Liam Feb 21 '19 at 10:58
  • 1
    @Liam If you like unwrapping tasks and making your code larger without any benefit then yes. – FCin Feb 21 '19 at 10:59
  • 3
    @Liam It means your readers now need to thoroughly understand two separate asynchrony models, one of which is not at all intuitive in many cases. Almost anything that ContinueWith can do, async/await can accomplish in a more readable way. – canton7 Feb 21 '19 at 10:59
  • Possible duplicate of [How to cancel a Task in await?](https://stackoverflow.com/questions/10134310/how-to-cancel-a-task-in-await) – Liam Feb 21 '19 at 11:01
  • I included the whole function now. so its more clear what it does – BlueDragon709 Feb 21 '19 at 11:01
  • 2
    @Liam In this case, for example, would you have guessed that `ContinueWith` doesn't have an overload which task a `Func`, and therefore returns a `Task`. Therefore the parent Task won't wait until its child completes, just until the `SendAsync` method has been called. What you actually need to do is `await await Task.Delay(...)...`, or use `.Unwrap()`. No, most people would get lost in the middle of this. Use ContinueWith if you want fork/join with synchronous code only, or async/await if you want asynchrony. Don't mix them. – canton7 Feb 21 '19 at 11:02
  • 3
    @BlueDragon709 use a CancellationTokenSource and pass a CancellationToken in any asynchronous operation that accepts it, including `Task.Delay` and `ContinueWith`. That `Task.Delay(300000).ContinueWith` is strange though, why not call and await each operation separately? – Panagiotis Kanavos Feb 21 '19 at 11:05
  • 1
    I am rewriting a Node socket.io server to a asp.netcore signalr server. so i tried to translate a JavaScript setTimeOut to C# @PanagiotisKanavos – BlueDragon709 Feb 21 '19 at 11:08
  • @BlueDragon709 that's a completely different question - how to implement a timeout. A CancellationTokenSource can have a timeout parameter. `Node` and Javascript got `async/await` from C#, with several limitations due to language and environment restrictions. Don't try to copy *that* style. – Panagiotis Kanavos Feb 21 '19 at 11:10
  • @BlueDragon709 as for SignalR, all [SendAsync](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.client.hubconnectionextensions.sendasync?view=aspnetcore-2.2) overloads accept a CancellationToken – Panagiotis Kanavos Feb 21 '19 at 11:15
  • I put the piece of Javascript code that i want to rewrite to C# as edit in the question. – BlueDragon709 Feb 21 '19 at 11:18
  • @BlueDragon709 I still don't understand *when* you want to cancel the timeout? – canton7 Feb 21 '19 at 11:20
  • @BlueDragon709 it doesn't help at all because it's neither about SignalR nor about .NET tasks. As I said, if you want to cancell a task, create a CancellationToken. What you really want though can't be done using that Javascript code. You don't want a timeout. You want to do something after 5 minutes of *inactivity*. Neither your Javascript code nor the C# code checks for logged in users though. Both just delete all rooms after 5 minutes – Panagiotis Kanavos Feb 21 '19 at 11:21

1 Answers1

0

To allow the original thread to cancel a task you need to pass a cancellation token and then flag the cancellation through the cancellation source.

public class Program
{
  public static void Main()
  { 
    CancellationTokenSource source = new CancellationTokenSource();
    CancellationToken token = source.Token;
    var task=AsyncMain(token);
    source.Cancel();
    try
    {
    Console.WriteLine("Main after started thread");
    task.Wait();
    Console.WriteLine("Main after task finished");
    }
    catch (AggregateException )
    {
    Console.WriteLine("Exceptions in Task");
    }
  }

  public static async Task AsyncMain(CancellationToken token)
  {
    Console.WriteLine("In Thread at Start");
    try
    {
      await Task.Delay(10).ContinueWith(
        async task =>
        {
          Console.WriteLine("Not Cancelled");
        }
        ,token);
    }
    catch(OperationCanceledException )
    {
      Console.WriteLine("Cancelled");
    }
    Console.WriteLine("In Thread after Task");
  }
}

However as other people have noted ContinueWith is mixing paragims and not needed in this case. For example you could do;

public static async Task AsyncMain(CancellationToken token)
{
    Console.WriteLine("In Thread at Start");
    await Task.Delay(10);
    if(!token.IsCancellationRequested)
    {
        //await Clients.Caller.SendAsync("NoUsersFound");
        Console.WriteLine("Not Cancelled");
    }
    else
    {
        Console.WriteLine("Cancelled");
    }
}

Or you could just check if the user list is empty and bypass the need to raise a task cancellation.

Taemyr
  • 3,407
  • 16
  • 26
  • The `ContinueWith` part will still be executed unless you provide `TaskContinuationOptions,OnlyOnRanToCompletion` as an option – NineBerry Feb 21 '19 at 11:37
  • 1
    Also, as aothers have said, why use ContinueWith in an async context? Just use normal await – NineBerry Feb 21 '19 at 11:38
  • 1
    Just `await Task.Delay(..., token); Console.WriteLine("Not Cancelled")`. So much clearer. – canton7 Feb 21 '19 at 11:40
  • Also, always catch `OperationCanceledException` not `TaskCanceledException`. Some things throw `OperationCanceledException` on cancellation. – canton7 Feb 21 '19 at 11:40
  • @NineBerry The CancellationToken is passed to ContinueWith; not to Delay. – Taemyr Feb 21 '19 at 11:43
  • 1
    @Taemyr it should be passed to `Task.Delay`. It does nothing if it's passed to `ContinueWith` – canton7 Feb 21 '19 at 11:45
  • The token should be passed to both Delay and ContinueWith. – NineBerry Feb 21 '19 at 11:46
  • 1
    Fair. I still contend that it should be passed to `Task.Delay` to actually cancel the timeout. And once you're doing that, there's no point in passing it to the `ContinueWith` – canton7 Feb 21 '19 at 11:48
  • @canton7 It's a moot point. The correct approach is to not use ContinueWith. And I am not convinced about the need for a cancellation at all. My intuition is that its cheaper to just do a .Any on the list of users that have joined the room. – Taemyr Feb 21 '19 at 11:48