【问题标题】:Creating and stopping threads from listview从 listview 创建和停止线程
【发布时间】:2021-04-03 04:44:51
【问题描述】:

我需要你的帮助,线程我满 0,你只需要创建一个特定线程并在命令上完成它,但是我不会提前创建每个线程,因为它们会很多,我这样做:

Thread thread = new Thread(() => Go(..... many many variables that are taken from the listview ......));
thread.Start();

因此,如上所述,变量是从列表视图中获取的,而列表视图又由我从文件中加载,然后我运行我需要的线程。但是流中的过程是无限的,只有在我完全关闭程序时才会结束,并且我想以与启动它相同的方式结束流(右键单击所需的行开始/停止)。正如我所说,我从未使用过线程并且认为它在某种程度上很简单,就像当你启动一个线程时,你给它分配一个 ID 并以相同的 ID 结束它,但是唉。我在 Google 上搜索了所有内容,但没有找到适合我的示例(我将第三次重复 - 我从未使用过线程,我不需要说“去阅读 TPL”),所以我寻求帮助,最好有例子)

我有一个非常糟糕的主意:在工作表中有一个不可见的列,其中在开始时会生成一个 id,然后当我发送命令启动线程时,会创建一个唯一变量,其名称例如 int id1=0 及其名称被传递给线程本身,每次循环开始时,id1=0 或 1 分别在其中检查,如果 0-继续,如果 1-空。好吧,当您单击停止按钮时,其值变为 1 是合乎逻辑的。但在我看来,当线程数变为 100+ 时,多线程的神圣精神会为此惩罚我。我在某处读过这个想法,所以不要发誓)

【问题讨论】:

  • 在现代 c# 中,您可能不应该直接使用线程(除非您是非常高级的开发人员并且有令人信服的理由)。还有许多其他语言功能可以处理多个任务,例如async Task、TPL 和BackgroundWorker。我建议您描述您正在尝试解决的更大问题,并且有人可能会提供一种简单的方法来解决。
  • 嗯,你看,我有一个加载账户列表的程序(可以有5个、10个或100个,也就是我不知道线程数提前)。所有帐户都被加载到列表视图中,并从那里使用上下文菜单在执行 http 命令的无尽流中运行。在我看来一切正常,但问题是我只能通过完全关闭程序来完成执行,如果有必要我想单独关闭每个帐户,我无法想象如何从相同的上下文中执行此操作菜单。
  • 启动后,一切都清楚了,字符串中的数据在启动时传递给流并传递给方法,如果可以为每个线程分配一些唯一标识符并终止必要的用这个 id 线程,那么一切都会好起来的。我不需要将它们相互同步等等,只需单独启动/停止每个线程)
  • 您可能应该考虑使用任务和取消令牌。此外,这里还有女性。
  • @TonyPak - 在现代 C# 中,您不能停止线程。你必须有线程的合作才能结束。解决这个问题的唯一方法是在单独的进程中运行新线程并终止该进程,这很麻烦。

标签: c# multithreading


【解决方案1】:

您不需要数百个线程。您的工作“线程”正在执行 HTTP 请求,这可以异步完成而无需新线程。此外,除非您有数百个 CPU 内核(您没有),否则数百个线程不会真正帮助您。

对于此类工作,我建议以下内容:

  1. 编写一个方法来完成您的线程所做的所有工作,但还要在每次迭代时检查CancellationToken

  2. 在循环中调用该方法,为每个帐户调用一次,并将生成的任务存储在数组或列表中。或者使用 LINQ(就像我在本例中所做的那样)来创建列表。

  3. 当您的程序终止时,激活CancellationToken

  4. 取消后,你必须await所有任务,以便观察任何可能的异常并干净地退出。

例如

public async Task DoTheWork(Account account, CancellationToken token)
{
    while (!token.IsCancellationRequested)
    {
        var result = await httpClient.GetAsync(account.Url);
        await DoSomethingWithResult(result);
        await Task.Delay(1000);
    }
}

//Main program
var accounts = GetAccountList();
var source = new CancellationTokenSource();
var tasks = accounts.Select( x => DoTheWork(x, source.Token) ).ToList();


//When exiting
source.Cancel();
await Task.WhenAll( tasks );
source.Dispose();    

个别取消

这是另一种方法,它保留帐户列表和可用于取消该特定帐户任务的委托。

//Declare this somewhere it will persist for the duration of the program
//The key to this dictionary is the account you wish to cancel
//The value is a delegate that you can call to cancel its task
Dictionary<Account, Func<Task>> _tasks = new Dictionary<Account, Func<Task>>();

async Task CreateTasks()
{
    var accounts = GetAccounts();
    foreach (var account in accounts)
    {
        var source = new CancellationTokenSource();
        var task = DoTheWork(account, source.Token);
        _tasks.Add(account, () => { source.Cancel(); return task; });
    }
}

//Retrieve the delegate from the dictionary and call it to cancel its task
//Then await the task to observe any exceptions
//Then remove it from the list
async Task CancelTask(Account account)
{
    var cancelAction = _tasks[account];
    var task = cancelAction();
    await task;
    _tasks.Remove(account);
}

async Task CancelAllTasks()
{
    var tasks = _tasks.Select(x => x.Value()).ToList();
    await Task.WhenAll(tasks);
}
    

【讨论】:

  • 非常感谢您的提示,但这对我在必要时停止特定帐户有何帮助?手动停止它,而不是在满足某些条件时停止。
  • 据我了解,您的示例将帮助我一次启动和停止所有任务?还是我误会了什么?
  • 您可以为每个帐户创建一个 CancellationTokenSource。
  • @TonyPak 有几种方法可以做到这一点 - 请参阅我编辑的答案以了解一种方法。这种方法有点“聪明”,因为它使用捕获的变量而不是声明正确的数据结构。让我知道这是否太令人困惑,我可以向您展示一种不使用委托的方法。
  • @JohnWu 如果我写点什么我会尽力理解,非常感谢!
猜你喜欢
  • 1970-01-01
  • 2019-06-18
  • 2020-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多