【问题标题】:Is CancellationTokenSource shutting down correctlyCancellationTokenSource 是否正确关闭
【发布时间】:2016-08-26 14:59:11
【问题描述】:

这里是入口点:

public class Program
{
    private static readonly CancellationTokenSource TokenSource = new CancellationTokenSource();

    public static void Main(string[] args)
    {
        // start the app
        new Bootstrap()
            .RunAsync(TokenSource.Token)
            .Wait();

        Console.CancelKeyPress += (sender, eventArgs) =>
        {
            TokenSource.CancelAfter(500);
        };
    }
}

这是引导程序:

public class Bootstrap : IBootstrap
{
    private readonly IServer server;

    private readonly ILogger logger;

    public Bootstrap(
        IServer server,
        ILogger logger)
    {
        this.server = server;
        this.logger = logger;
    }

    public async Task RunAsync(CancellationToken token)
    {
        this.logger.Info("Application Starting...");

        await this.server.StartAsync(token);
    }
}

这是服务器:

public abstract class BaseServer : IServer
{
    private readonly IPAddress ipAddress;

    private readonly int port;

    private readonly ILogger logger;

    protected BaseServer(
        string ipAddress, 
        int port,
        ILogger logger)
    {
        this.ipAddress = IPAddress.Parse(ipAddress);
        this.port = port;
        this.logger = logger;
    }

    public async Task StartAsync(CancellationToken token)
    {
        this.logger.Debug("[{0}] Listening for connections using: {1}:{2}", this.GetType().Name, this.ipAddress.ToString(), this.port);
        var tcpListener = new TcpListener(this.ipAddress, this.port);

        tcpListener.Start();

        while (!token.IsCancellationRequested)
        {
            await this.ServerProcessorAsync(tcpListener, token);
        }

        tcpListener.Stop();
        Console.WriteLine("Stopped Listener");
    }

    public abstract Task ServerProcessorAsync(TcpListener listener, CancellationToken token);
}

这是服务器处理器:

public class Server : BaseServer
{
    private readonly ILogger logger;

    public Server(
        IAppConfiguration configuration,
        ILogger logger) 
        : base(configuration.IpAddress, configuration.Port, logger)
    {
        this.logger = logger;
    }

    public override async Task ServerProcessorAsync(TcpListener listener, CancellationToken token)
    {
        this.logger.Debug("[{0}] Waiting for connection...", this.GetType().Name);
        var client = await listener.AcceptTcpClientAsync();

        this.logger.Debug("[{0}] Spawning Thread for Connection...", this.GetType().Name);
        Parallel.Invoke(new ParallelOptions
            {
                CancellationToken = token,
                MaxDegreeOfParallelism = 10000,
                TaskScheduler = TaskScheduler.Current
            }, () => this.ListenToClient(client));
    }

    private void ListenToClient(TcpClient client)
    {
        var threadName = Thread.CurrentThread.Name;

        var bytes = new byte[2048];

        var stream = client.GetStream();

        int i;
        while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
        {
            var timeString = DateTime.Now.ToLongTimeString();
            var currentRes = Encoding.UTF8.GetString(bytes);
            var received = $"Recieved [{threadName}] [{timeString}]:  {currentRes}";

            this.logger.Info(received);
            var responseData = Encoding.UTF8.GetBytes(received);
            stream.Write(responseData, 0, responseData.Length);

        }

        client.Close();
    }
}

当按下ctrl+c 时,这会正确关闭应用程序吗?

有没有办法调试这个,或者知道资源已经被正确释放了。

我假设while (!token.IsCancellationRequested) 将在ctrl+c 时中断。我还假设如果Parallel.Invoke 创建了任何线程,它们将在调用Cancel 时被处理掉。

如果在这种情况下:

Console.CancelKeyPress += (sender, eventArgs) =>
{
     TokenSource.CancelAfter(500);
};

不等待事情被清除,有没有比超时更好的方法来确保一切都被正确清除?

【问题讨论】:

  • 我想如果你想等待任务被取消后返回,你必须在取消令牌后Wait()该任务。
  • 当按下 ctrl+c 时,这会正确关闭应用程序吗?您是否通过键入 Crtl+C 进行测试以查看?
  • ctrl+c 将关闭应用程序,是否正确完成是个问题。

标签: c#


【解决方案1】:

首先,您在订阅 Console.CancelKeyPress 事件之前等待 RunAsync ,因此您将在为时已晚时订阅它。

其次,取消令牌在您的情况下无论如何都不起作用。这一行:

var client = await listener.AcceptTcpClientAsync();

将阻塞直到新客户端连接,并且因为AcceptTcpClientAsync 确实 具有接受CancellationToken 的重载 - 不需要在整个程序中使用CancellationTokens。你应该做的是停止你的听众而不是取消。这将在上面的行中抛出异常,您应该捕获并优雅地结束任务。

如果您真的想继续使用 CancellationToken,即使这里并不真正需要它,请考虑使用此方法使其与 AcceptTcpClientAsynchttps://stackoverflow.com/a/14524565/5311735 一起使用。如果您使用 CancellationToken 取消许多未在您的问题中显示的不同操作,这也可能是个好主意。

【讨论】:

  • 请注意,您还需要在 CancelKeyPress 事件处理程序中等待您的任务完成,否则您的进程将在清理任何内容之前停止。
  • 还有更多:您正在将 一个 操作传递给 Parallel.Invoke,这意味着 Parallel.Invoke 根本没有任何用处。甚至不知道你是什么意思。
  • 我阅读了 Jon Skeet 写的 stackoverflow 帖子回答说,每次连接新客户端时,您可以使用 Parallel.Invoke 生成新线程。
  • 你有那个答案的链接吗?
  • 他没有明确说明如何做,但确实说要使用 TPL stackoverflow.com/questions/5339782/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-02
相关资源
最近更新 更多