【问题标题】:How check "IsAlive" status of c# async Thread? [duplicate]如何检查 c# 异步线程的“IsAlive”状态? [复制]
【发布时间】:2020-06-30 18:00:41
【问题描述】:

假设我的 Windows C# 应用中有一个 MyThread 类,如下所示:

public class MyThread
{
   Thread TheThread;

   public MyThread()
   {
      TheThread = new Thread(MyFunc);
   }

   public void StartIfNecessary()
   {
      if (!TheThread.IsAlive)
         TheThread.Start();
   }

   private void MyFunc()
   {
      for (;;)
      {
          if (ThereIsStuffToDo)
             DoSomeStuff();
      }
   }
}

效果很好。但是现在我意识到我可以通过使用 async/await 来提高我的线程效率:

public class MyThread
{
   Thread TheThread;

   public MyThread()
   {
      TheThread = new Thread(MyFunc);
   }

   public void StartIfNecessary()
   {
      if (!TheThread.IsAlive)
         TheThread.Start();
   }

   private async void MyFunc()
   {
      for (;;)
      {
         DoSomeStuff();
         await MoreStuffIsReady();
      }
   }
}

我现在看到的是,我第二次调用 StartIfNecessary() 时,TheThread.IsAlive 为假(顺便说一下,ThreadState 已停止)所以它调用 TheThread.Start(),然后抛出 ThreadStateException“线程正在运行或终止; 它无法重新启动”。但是我可以看到 DoMoreStuff() 仍在被调用,因此该函数实际上仍在执行。

我怀疑发生了什么,当我的线程遇到“等待”时,我创建的线程被停止,当 MoreStuffIsReady() 上的等待完成时,线程池中的一个线程被分配执行 DoSomeStuff() .因此,从技术上讲,我创建的线程已停止,但我创建的要处理的线程的函数仍在运行。

那么如何判断“MyFunc”是否仍处于活动状态?

我能想到 3 种方法来解决这个问题:

1) 添加一个“bool IsRunning”,在调用 TheThread.Start() 之前设置为 true,而 MyFunc() 在完成时设置为 false。这很简单,但需要我将所有内容都包装在 try/catch/finally 中,这并不可怕,但我希望有一种方法可以让操作系统或框架在这里帮助我,以防万一“MyFunc”在某些情况下死掉出乎我的意料。

2) 在 System.Threading 中的某处找到一些新函数,它将为我提供所需的信息。

3) 重新考虑整个事情——因为我的线程只停留了几毫秒,有没有办法在完全不创建线程的情况下完成同样的功能(在线程池之外)?以某种方式将“MyFunc”作为任务启动?

这种情况下的最佳做法?

【问题讨论】:

  • 如果您能够重新开始,请发送Worker Service?
  • 天哪。谷歌“async void被认为有害”以取得成功。
  • 我可以通过使用 async/await 让我的线程更高效
  • MyFunc() 方法在遇到await 语句时立即返回。这就是那个线程的结尾。见标记重复。另请参阅 Stack Overflow 上数以千计的相关问题,在许多情况下实际上是相同的问题,这些问题有助于解释 async/await 的工作原理以及如何将这些技术应用到自己的代码中。

标签: c# asynchronous async-await task


【解决方案1】:

坚持使用普通旧线程并使用 BlockingCollection 避免紧密循环:

class MyThread
{
    private Thread worker = new Thread(MyFunc);
    private BlockingCollection<Action> stuff = new BlockingCollection<Action>();

    public MyThread()
    {
        worker.Start();
    }

    void MyFunc()
    {
        foreach (var todo in stuff.GetConsumingEnumerable())
        {
           try
           {
               todo();
           }
           catch(Exception ex)
           {
              // Something went wrong in todo()
           }
        }
        stuff.Dispose(); // should be disposed!
    }

    public void Shutdown()
    {
         stuff.CompleteAdding(); // No more adding, but will continue to serve until empty.
    }

    public void Add( Action stuffTodo )
    {
          stuff.Add(stuffTodo); // Will throw after Shutdown is called
    }
}

BlockingCollection 还显示了带有 Task 的示例,如果您更愿意走这条路。

【讨论】:

  • 通常您会使用GetConsumingEnumerable,而不是使用需要手动检查集合是否已完成或是否有任何元素的低级方式。 IE。 foreach (var todo in stuff.GetConsumingEnumerable()) 比 while 循环、Take 调用和 InvalidOperationException 的捕获要简单得多。
  • 很好的发现。会编辑。 - 完成。
【解决方案2】:

重新思考整个事情

这绝对是最好的选择。彻底摆脱线程。

您似乎有一个“消费者”类型的场景,并且您需要一个具有数据项缓冲区的消费者来处理。

一种选择是使用来自TPL DataflowActionBlock&lt;T&gt;

public class NeedsADifferentName
{
  ActionBlock<MyDataType> _block;

  public NeedsADifferentName() => _block = new ActionBlock<MyDataType>(MyFunc);

  public void QueueData(MyDataType data) => _block.Post(data);

  private void MyFunc(MyDataType data)
  {
    DoSomeStuff(data);
  }
}

或者,您可以使用Channels 之类的方式构建自己的管道。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-27
    • 1970-01-01
    • 2015-05-18
    • 1970-01-01
    • 2012-11-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多