【问题标题】:Set property value when all threads are finished?当所有线程完成时设置属性值?
【发布时间】:2017-11-03 10:40:42
【问题描述】:

在我的应用程序中有三个线程,例如:

 private Thread _analysisThread;
 private Thread _head2HeadThread;
 private Thread _formThread;

每个线程都以如下方式启动:

if (_analysisThread == null || !_analysisThread.IsAlive)
{
     _analysisThread = new Thread(() => { Analysis.Logic(match); });
     _analysisThread.Start();
}

我有一个 ListView,用户可以在其中选择一个项目,然后重新启动线程,但我想防止这种情况发生,因为每个线程内的方法都很繁重,所以需要时间来完成它们。

到目前为止,我想禁用 ListView 选择,所以我做到了:

<ListView IsEnabled="{Binding IsMatchListEnabled}">

private bool _isMatchListEnabled = true;
public bool IsMatchListEnabled
{
    get { return _isMatchListEnabled; }
    set
    {
        _isMatchListEnabled = value;
        OnPropertyChanged();
    }
}

在新线程开始之前我会:IsMatchListEnabled = false;,但我需要做的是检查所有线程是否已完成,然后执行:IsMatchListEnabled = true;,实际上如果我在所有线程之后启用ListView,我会得到ListView 甚至启用了,因为Thread 代码是异步的,而Thread 之外的代码是同步的,所以实际上这个属性是没有用的。

我试图避免这种情况是创建一个这样的无限循环:

while (true)
{
   if (!_analysisThread.IsAlive && !_head2HeadThread.IsAlive && !_formThread.IsAlive)
   {
          IsMatchListEnabled = true;
          break;
   }
}

这个循环被放置在所有线程执行之后,但正如你所想象的那样,这将冻结应用程序。 有什么解决办法吗?

【问题讨论】:

  • 使用Tasks 代替线程。 Task 提供正常的等待完成方式。如果您坚持,您可以使用 Threads 做到这一点,但没有太多理由这样做。
  • @Evk 是的,谢谢你的提示:
  • 您编写了即发即弃的线程代码。你几乎永远不会忘记。不要使用 Thread,使用方便的帮助类来帮助你不要忘记。 BackgroundWorker,Task,也许是 async/await。并且一定要仔细阅读这一点,当你理解得不够透彻时,穿线会把你活活吃掉并吐出你的骨头。

标签: c# multithreading


【解决方案1】:

所有 cmets 都是正确的 — 最好使用 Tasks。只是为了回答OP的问题。

您可以将线程与ManualResetEvent 同步,通过线程数和一个额外的线程来更改IsMatchListEnabled 的事件数组,当所有线程都完成时。

public static void SomeThreadAction(object id)
{
    var ev = new ManualResetEvent(false);
    events[id] = ev; // store the event somewhere

    Thread.Sleep(2000 * (int)id); // do your work

    ev.Set(); // set the event signaled
}

然后,我们需要在其他地方初始化等待程序。

// we need tokens to be able to cancel waiting
var cts = new CancellationTokenSource();
var ct = cts.Token;

Task.Factory.StartNew(() =>
{
    bool completed = false;
    while (!ct.IsCancellationRequested && !completed)
    {
        // will check if our routine is cancelled each second
        completed = 
            WaitHandle.WaitAll(
                events.Values.Cast<ManualResetEvent>().ToArray(),
                TimeSpan.FromSeconds(1)); 
    }

    if (completed) // if not completed, then somebody cancelled our routine
        ; // change your variable here
});

完整的例子可以在here找到和查看。

【讨论】:

    【解决方案2】:

    我建议为此使用 Microsoft 的反应式框架。它比任务更强大,代码也比使用线程简单得多。

    假设您有 3 个长时间运行的操作:

    Action huey = () => { Console.WriteLine("Huey Start"); Thread.Sleep(5000); Console.WriteLine("Huey Done"); };
    Action dewey = () => { Console.WriteLine("Dewey Start"); Thread.Sleep(5000); Console.WriteLine("Dewey Done"); };
    Action louie = () => { Console.WriteLine("Louie Start"); Thread.Sleep(5000); Console.WriteLine("Louie Done"); };
    

    现在您可以编写以下简单查询:

    IObservable<Unit> query =
        from a in new [] { huey, dewey, louie }.ToObservable()
        from u in Observable.Start(() => a())
        select u;
    

    你这样运行它:

    Stopwatch sw = Stopwatch.StartNew();
    IDisposable subscription = query.Subscribe(u => { }, () =>
    {
        Console.WriteLine("All Done in {0} seconds.", sw.Elapsed.TotalSeconds);
    });
    

    我得到的结果是:

    休伊开始 杜威开始 路易开始 休伊完成 路易完成 杜威完成 全部在 5.0259197 秒内完成。

    三个 5 秒的操作在 5.03 秒内完成。全部并行。

    如果您想提前停止计算,只需致电subscription.Dispose()

    NuGet "System.Reactive" 获取位。

    【讨论】:

    • 如果任何线程出现异常会发生什么?会在subscription.Subscribe附近重新抛出吗?
    • @cassandrad - 不,不是我发布的代码,但是.Subscribe 方法还有另一个重载,如果抛出一个异常,它将引发异常。你也可以在 observables 上加入 .Retry 操作符来重试一些由于异常而失败的代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 2018-12-02
    • 1970-01-01
    相关资源
    最近更新 更多