C# 静态代码如何与多个线程一起运行?

C# 静态代码如何与多个线程一起运行?,c#,.net,multithreading,C#,.net,Multithreading,我当时正在读书,我的处境也很相似 我有一个静态方法,它从资源中提取数据,并基于数据创建一些运行时对象 static class Worker{ public static MyObject DoWork(string filename){ MyObject mo = new MyObject(); // ... does some work return mo; } } 该方法需要一段时间(在本例中是读取5-10mb文件)并返

我当时正在读书,我的处境也很相似

我有一个静态方法,它从资源中提取数据,并基于数据创建一些运行时对象

static class Worker{
    public static MyObject DoWork(string filename){
        MyObject mo = new MyObject();

        // ... does some work

        return mo;
    }
}
该方法需要一段时间(在本例中是读取5-10mb文件)并返回一个对象

我想采用这种方法,并在多线程的情况下使用它,这样我就可以一次读取多个文件。撇开设计问题/准则不谈,多线程如何访问此代码

假设我有这样的东西

class ThreadedWorker {
    public void Run() {
        Thread t = new Thread(OnRun);
        t.Start();
    }

    void OnRun() {
        MyObject mo = Worker.DoWork("somefilename");

        mo.WriteToConsole();
    }
}

静态方法是否为每个线程运行,允许并行执行?

是的,该方法应该能够在多个线程中正常运行。您唯一应该担心的是同时在多个线程中访问同一文件。

如果静态方法被编写为线程安全的,那么它可以从任何线程调用,甚至可以传递到线程池。


您必须记住-.NET对象不存在于线程上(位于线程堆栈上的结构除外)-执行路径存在于线程上。因此,如果线程可以访问对象的实例,它可以调用实例方法。任何线程都可以调用静态方法,因为它只需要知道对象的类型。

在这种情况下,您应该区分静态方法和静态字段。对静态方法的每次调用都有自己的方法及其局部变量的“副本”。这意味着在您的示例中,每个调用都将在其自己的
MyObject
实例上运行,并且这些调用将彼此无关。这也意味着在不同的线程上执行它们没有问题。

静态方法将在调用它的线程上运行。只要您的函数是可重入的,这意味着当从另一个线程(或堆栈更高的线程)执行时,可以安全地重新进入函数

因为您的函数是静态的,所以您不能访问成员变量,这将是使其不可重入的一种方法。如果您有一个维护状态的静态局部变量,这将是另一种使其不可重入的方法

每次输入时,您都会创建一个新的MyObject,因此执行流的每一位都在处理自己的MyObject实例,这很好。这意味着他们不会在同一时间尝试访问同一对象(这将导致竞争条件)


您在多个调用之间共享的唯一内容是控制台本身。如果您在多个线程上调用它,它们将互相输出到控制台。您可能会对同一个文件执行操作(在您的示例中,文件名是硬编码的),但您可能会对多个文件执行操作。如果之前的线程打开了文件,则后续线程可能无法打开该文件。

在同时执行静态方法时,您应该记住的一点是静态字段,它只存在一次。因此,如果该方法读取和写入静态字段,则可能会出现并发问题

但是,有一个名为
ThreadStaticAttribute
的属性,表示每个线程都有一个单独的字段。这在某些特定场景中可能会有所帮助

局部变量对于每个线程都是独立的,所以您不必关心这一点。但是要注意像文件这样的外部资源,在并发访问时可能会出现问题

致以最诚挚的问候,

Oliver Hanappi

除了已经被回答的代码方面,您还需要考虑访问文件的I/O方面。

关于体系结构以及我过去是如何完成这项任务的说明—并不是说这是一种正确的方法,也不是说它必然适合您的应用程序。但是,我认为我的笔记可能会对您的思考过程有所帮助:

设置一个ManualResetEvent字段,称之为ActivateReader或类似的东西,这将在以后变得更加明显。将其初始化为false

设置一个布尔字段,称之为TerminateReaderThread。将其初始化为false,这将再次变得更加明显

设置一个队列字段,调用它并初始化它

在将每个相关文件路径写入文件队列之前,我的主应用程序线程会检查文件队列上是否有锁。写入文件后,将触发重置事件,向队列读取器线程指示队列中存在未读文件

然后我设置了一个线程作为队列读取器。此线程使用WaitAny()方法等待ManualResetEvent跳闸-这是一种阻塞方法,一旦ManualResetEvent跳闸,就会解除阻塞。一旦触发,线程将检查线程关闭是否已启动[通过检查TerminateReaderThread字段]。如果启动了关闭,线程将正常关闭,否则它将从队列中读取下一项,并生成一个工作线程来处理该文件。然后我锁定队列,然后检查是否还有剩余的项目。如果没有留下任何项目,我将重置ManualResetEvent,它将在下一次循环时暂停线程。然后我解锁队列,以便主线程可以继续写入它

工作线程的每个实例都会尝试在启动时使用的文件上获得独占锁,直到超时结束。如果锁定成功,它将处理该文件;如果锁定不成功,它将根据需要重试,抛出异常并自行终止。在发生异常的情况下,线程可以将文件添加到队列的末尾,以便另一个线程可以在稍后再次拾取该文件。请注意,如果您这样做,那么您需要考虑I/O读取问题可能导致的无休止循环。在这种情况下,故障文件字典(带有故障次数计数器)可能非常有用,这样,如果达到某个限制,您就可以停止将文件重新添加到队列末尾<