如何停止在后台运行的线程工作?

huangapple go评论63阅读模式
英文:

How can stop thread worker , which is run background?

问题

在我的项目中,我正在使用异步和等待,现在我想要停止后台运行的工作线程,我应该如何做呢?

我尝试了以下方法:

private CancellationTokenSource cancellationToken;

private async void method1(int parameter) {
  //做一些事情
}

private async Task method2(int parameter) {
  await Task.Run(() => {
    method1(parameter);
    Task.Delay(1000).Wait();
  },
  cancellationToken.Token);
}

private async void bt_start_Click(object sender, EventArgs e) {
  cancellationToken = new CancellationTokenSource();
  await method2(123);
}

private async void bt_stop_Click(object sender, EventArgs e) {
  if (cancellationToken != null) {
    cancellationToken.Cancel();
  }
}

请注意,这是您提供的代码的翻译,代码部分未进行任何更改。

英文:

I using async and await in my project , now i want to stop worker thread, which is running background, How can i do it

如何停止在后台运行的线程工作?

I try with this below

private CancellationTokenSource cancellationToken;

private async void method1(int parameter) {
  //do something
}

private async Task method2(int parameter) {
  await Task.Run(() =>{
    method1(parameter);
    Task.Delay(1000).Wait();
  },
  cancellationToken.Token);
}

private async void bt_start_Click(object sender, EventArgs e) {
  cancellationToken = new CancellationTokenSource();
  await method2(123);
}

private async void bt_stop_Click(object sender, EventArgs e) {
  if (cancellationToken != null) {
    cancellationToken.Cancel();
  }
}

答案1

得分: 1

以下是翻译好的代码部分:

// Cancellation is a cooperative process in .net,
// So we should pass token...
private async Task method2(int parameter, CancellationToken token = default) {
  await Task.Run(async () => {
    //TODO: you may want to pass token to method1 as well
    method1(parameter);

    // ... and cancel using this token
    await Task.Delay(1000, token);
  });
}

private CancellationTokenSource cancellationToken;

private async void bt_start_Click(object sender, EventArgs e) {
  // 注意,CancellationTokenSource 是可 IDisposable 的,因此应该被释放
  try {
    using (cancellationToken = new CancellationTokenSource()) {
      await method2(123, cancellationToken.Token);
    }
  }
  catch (TaskCanceledException) {
    // method2 has been cancelled
  }
  finally {
    // 我们不能使用已释放的 CancellationTokenSource;我们应该重新创建它
    cancellationToken = null;
  }
}

// No await here, so async is not necessary 
private void bt_stop_Click(object sender, EventArgs e) {
  // Cancel if we can do it 
  if (cancellationToken != null) {
    cancellationToken.Cancel();
  }
}

请注意,这是您提供的代码的翻译版本,没有其他内容。

英文:

Something like this:

// Cancellation is a cooperative process in .net,
// So we should pass token...
private async Task method2(int parameter, CancellationToken token = default) {
  await Task.Run(async () => {
    //TODO: you may want to pass token to method1 as well
    method1(parameter);

    // ... and cancel using this token
    await Task.Delay(1000, token);
  });
}

Then

private CancellationTokenSource cancellationToken;

private async void bt_start_Click(object sender, EventArgs e) {
  // Note, that CancellationTokenSource is IDisposable and thus should be disposed
  try {
    using (cancellationToken = new CancellationTokenSource()) {
      await method2(123, cancellationToken.Token);
    }
  }
  catch (TaskCanceledException) {
    // method2 has been cancelled
  }
  finally {
    // We can't use disposed CancellationTokenSource; we should re-create it
    cancellationToken = null;
  }
}

// No await here, so async is not necessary 
private void bt_stop_Click(object sender, EventArgs e) {
  // Cancel if we can do it 
  if (cancellationToken != null) {
    cancellationToken.Cancel();
  }
}

答案2

得分: 0

.Net任务和线程如果已经在运行,就无法在没有它们的配合下停止。

您的代码正确地创建了取消令牌,并将其正确地传递给了 Task.Run 方法。如果在任务可以被调度并开始运行之前调用了 cancellationToken.Cancel(),它将直接进入取消状态,甚至不会开始执行。

但是一旦任务开始执行,您的代码中就没有任何后续的取消检查。这意味着如果任务开始执行,它将执行直到完成,无法取消。

大多数时候,我的代码实际上并没有做太多事情,它只是调用其他长时间运行的方法。这些方法通常会接受一个 cancellationToken 参数,所以我只需要将它传递给它们。它们会检查该令牌,如果它被取消,就会抛出一个 OperationCanceledException。在您的情况下,您已经调用了其中一个方法,Task.Delay,所以您只需要传递一个令牌。

有时,我的代码正在进行长时间运行的工作。在这种情况下,您自己必须定期调用 cancellationToken.ThrowIfCancellationRequested(),如果令牌被取消,它将抛出异常,否则什么都不会发生。您可以根据自己的判断来决定多久检查一次是否取消。在紧密循环中调用一次,仅仅是在一些数字上加法操作,可能太频繁了;而每分钟调用一次可能还不够频繁。

以下是对您的代码进行的一些修改示例:

private CancellationTokenSource cancellationTokenSource;

// 始终使用 async Task。async void 只用于 UI 事件处理程序。
private async Task method1(int parameter, CancellationToken cancellationToken) {
  //做一些事情

  //自己的代码,速度较慢,可以通过取消来改进
  for (var i = 0; i < 100; i++) {
    //在外部循环中检查取消
    cancellationToken.ThrowIfCancellationRequested();
    for (var j = 0; j < 10_000; j++) {
      //一些紧密的内部循环代码,在这里不进行检查
    }
  }

  for (var i = 0; i < 100; i++) {
    //在调用长时间运行的库代码时,只需传递令牌
    await someObject.SomeLongMethod(cancellationToken);
    await someObject.SomeOtherMethod(cancellationToken);
  }
}

// 标准做法是将 cancellationToken 作为最后一个参数
private async Task method2(int parameter, CancellationToken cancellationToken) {
  await Task.Run(async () =>{
    // 不要忘记 await!
    await method1(parameter, cancellationToken);
    await Task.Delay(1000, cancellationToken);
  },
  cancellationToken);
}

private async void bt_start_Click(object sender, EventArgs e) {
  // 取消旧的操作,如果仍在运行
  CancelAndClear();
  
  cancellationTokenSource = new CancellationTokenSource();
  await method2(123, cancellationTokenSource.Token);
  ClearCts();
}

// 这里不需要异步
private void bt_stop_Click(object sender, EventArgs e) {
  CancelAndClear();
}

private void CancelAndClear() {
  if (cancellationTokenSource != null) {
    cancellationTokenSource.Cancel();
    ClearCts();
  }
}

private void ClearCts() {
  if (cancellationTokenSource != null) {
    // 在它不会再被取消时必须释放 CTS
    cancellationTokenSource.Dispose();
    cancellationTokenSource = null;
  }
}
英文:

.Net tasks and threads can't be stopped without their cooperation if they're already running.

Your code correctly creates the cancellation token and correctly gives it to the Task.Run method. If you call cancellationToken.Cancel() before the task can get scheduled and starts running, it will go straight to cancelled state without even starting execution.

But once the task starts executing, there is nothing in your code doing any subsequent cancellation checks. This means that if the task started executing, it will execute until it is done, without cancellation.

Most often, my own code doesn't really do that much, it only calls other long-running methods. Those methods usually accept a cancellationToken argument so all I need to do is give it to them. They will check the token and throw an OperationCanceledException if it gets canceled. In your case, you already call one of those methods, Task.Delay, so you just need to pass it a token.

Sometimes, my own code is doing the long-running work. In that case, you yourself must periodically call cancellationToken.ThrowIfCancellationRequested(), which will throw if the token got canceled and do nothing otherwise. How often you check for cancellation like this is up to you -- use your own judgement. Calling it once per tight loop which just adds some numbers is too often, calling it once every minute is probably not often enough.

Here's some modifications to your code as an example.

private CancellationTokenSource cancellationTokenSource;

// Always use async Task. async void is only for UI event handlers.
private async Task method1(int parameter, CancellationToken cancellationToken) {
  //do something

  //own code which is slow and could be improved by cancellation
  for (var i = 0; i < 100; i++) {
    //checking for cancellation in the outer loop
    cancellationToken.ThrowIfCancellationRequested();
    for (var j = 0; j < 10_000; j++) {
      //some tight inner loop code, not checking here
    }
  }

  for (var i = 0; i < 100; i++) {
    //when calling long-running library code, just passing on the token
    await someObject.SomeLongMethod(cancellationToken);
    await someObject.SomeOtherMethod(cancellationToken);
  }
}

// standard practice is to take cancellationToken as the last argument
private async Task method2(int parameter, CancellationToken cancellationToken) {
  await Task.Run(async () =>{
    // don't forget await!
    await method1(parameter, cancellationToken);
    await Task.Delay(1000, cancellationToken);
  },
  cancellationToken);
}

private async void bt_start_Click(object sender, EventArgs e) {
  // canceling old operation, if still running
  CancelAndClear();
  
  cancellationTokenSource = new CancellationTokenSource();
  await method2(123, cancellationTokenSource.Token);
  ClearCts();
}

// no need for async here
private void bt_stop_Click(object sender, EventArgs e) {
  CancelAndClear();
}

private void CancelAndClear() {
  if (cancellationTokenSource != null) {
    cancellationTokenSource.Cancel();
    ClearCts();
  }
}

private void ClearCts() {
  if (cancellationTokenSource != null) {
    // CTS must be disposed after it won't get canceled anymore
    cancellationTokenSource.Dispose();
    cancellationTokenSource = null;
  }
}

huangapple
  • 本文由 发表于 2023年7月3日 17:29:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76603478.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定