【问题标题】:How can i cancel a Task that uses Continuewith?如何取消使用 Continuewith 的任务?
【发布时间】:2019-07-15 06:19:56
【问题描述】:

我在一个函数中有一个任务,这是整个函数:

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);
    });
}

我在 Stackoverflow 上找到了一些东西,但我不知道如何实现它们。

但我希望能够在其他功能中取消此任务。
我该怎么做?

编辑:这是我想重写为 C# 的一段 javascript 代码:

setTimeout(function (){
    socket.emit('NoUsersFound');
    delete AllRooms[data.room];
},  300000);

【问题讨论】:

  • 如果你使用 async/await,不要使用.ContinueWith
  • @Liam 这意味着您的读者现在需要彻底理解两个独立的异步模型,其中一个在许多情况下根本不直观。几乎所有 ContinueWith 能做的事情,async/await 都能以更易读的方式完成。
  • @Liam 例如,在这种情况下,您是否会猜到ContinueWith 没有重载,该任务是Func&lt;Task, Task&gt;,因此返回Task&lt;Task&gt;。因此,父任务不会等到其子任务完成,而是等到调用了SendAsync 方法。你真正需要做的是await await Task.Delay(...)...,或者使用.Unwrap()。不,大多数人会在这中间迷路。如果您只想使用同步代码进行 fork/join,请使用 ContinueWith,如果您想要异步,请使用 async/await。不要混合它们。
  • @BlueDragon709 使用 CancellationTokenSource 并在任何接受它的异步操作中传递 CancellationToken,包括 Task.DelayContinueWithTask.Delay(300000).ContinueWith 很奇怪,为什么不分别调用和等待每个操作呢?

标签: c# timeout signalr task


【解决方案1】:

要允许原始线程取消任务,您需要传递取消令牌,然后通过取消源标记取消。

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");
  }
}

但是,正如其他人所指出的那样,ContinueWith 混合了 paragims,在这种情况下不需要。例如你可以这样做;

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");
    }
}

或者您可以只检查用户列表是否为空并绕过引发任务取消的需要。

【讨论】:

  • ContinueWith 部分仍将执行,除非您提供 TaskContinuationOptions,OnlyOnRanToCompletion 作为选项
  • 另外,正如其他人所说,为什么在异步上下文中使用 ContinueWith ?只需使用普通等待
  • 只是await Task.Delay(..., token); Console.WriteLine("Not Cancelled")。清晰多了。
  • @Taemyr 它应该被传递给Task.Delay。如果传递给ContinueWith,它什么也不做
  • 公平。我仍然认为应该将它传递给Task.Delay 以实际取消超时。一旦你这样做了,就没有必要将它传递给ContinueWith
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-28
  • 2020-08-05
  • 1970-01-01
  • 1970-01-01
  • 2018-02-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多