我正在阅读Threading from within a class with static and non-static methods,我处于类似情况。
我有一个静态方法,它从资源中提取数据并根据数据创建一些运行时对象。
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();
}
}
静态方法是否针对每个线程运行,允许并行执行?
答案 0 :(得分:19)
是的,该方法应该能够在多个线程中正常运行。您唯一需要担心的是同时访问多个线程中的同一文件。
答案 1 :(得分:8)
在这种情况下,您应该区分静态方法和静态字段。每次调用静态方法都有自己的方法及其局部变量的“副本”。这意味着在您的示例中,每个调用都将在其自己的MyObject
实例上运行,并且调用将彼此无关。这也意味着在不同的线程上执行它们没有问题。
答案 2 :(得分:3)
如果静态方法被编写为线程安全的,那么它可以从任何线程调用,甚至可以传递给线程池。
你必须记住 - .NET对象不在线程上(除了位于线程堆栈上的结构) - 执行路径。因此,如果一个线程可以访问一个对象的实例,它可以调用一个实例方法。任何线程都可以调用静态方法,因为它需要知道的是对象的类型。
答案 3 :(得分:2)
在同时执行静态方法时应该记住的一件事是静态字段,它只存在一次。因此,如果该方法读取和写入静态字段,则可能发生并发问题。
但是,有一个名为ThreadStaticAttribute
的属性,它表示每个线程都有一个单独的字段。这在某些特定情况下会有所帮助。
每个线程的局部变量都是separte,因此您无需关心这一点。但请注意文件等外部资源,这些资源在并发访问时可能会出现问题。
最诚挚的问候,
Oliver Hanappi
答案 4 :(得分:2)
除了已经回答的代码方面之外,您还需要考虑访问该文件的I / O方面。
关于体系结构的说明以及我过去如何完成此任务 - 并未建议这是一种正确的方法,或者它必然适合您的应用程序。但是,我认为我的笔记可能对您的思考过程有所帮助:
设置一个ManualResetEvent字段,称之为ActivateReader或类似的东西,这将更加明显。将其初始化为false。
设置一个布尔字段,称之为TerminateReaderThread。将其初始化为false,这将再次变得更加明显。
设置队列< string>字段,称之为文件并初始化它。
我的主应用程序线程在将每个相关文件路径写入其中之前检查文件队列是否存在锁定。一旦文件被写入,重启事件将被触发,向队列读取器线程指示队列中有未读文件。
然后我设置一个线程作为队列读取器。此线程等待使用WaitAny()方法触发ManualResetEvent - 这是一个阻塞方法,一旦ManualResetEvent被触发就会解除阻塞。一旦它被触发,线程就会检查是否已经启动线程关闭[通过检查TerminateReaderThread字段]。如果已启动关闭,则线程将正常关闭,否则它将从队列中读取下一个项目并生成工作线程以处理该文件。然后我检查队列,然后检查是否还有任何项目。如果没有剩下任何项目,我重置ManualResetEvent,它会在下次复飞时暂停我们的线程。然后我解锁队列,以便主线程可以继续写入它。
工作线程的每个实例都尝试对其启动的文件进行独占锁定,直到超时,如果锁定成功,则处理文件,如果不成功,则根据需要重试,抛出异常并终止自身。在发生异常的情况下,线程可以将文件添加到队列的末尾,以便另一个线程可以在以后再次拾取它。请注意,如果执行此操作,则需要考虑I / O读取问题可能导致的无限循环。在这种情况下,失败文件的字典以及它们失败的次数的计数器可能是有用的,这样如果达到某个限制,您可以停止将文件重新添加到队列的末尾。
一旦我的应用程序决定不再需要读者线程,它就会将TerminateReaderThread字段设置为true。下次读取器线程循环到其进程的开始时,将关闭其关闭过程。
答案 5 :(得分:1)
静态方法将在您调用它的线程上运行。只要您的函数是 re-entrant ,意味着执行可以安全地重新进入函数,而从另一个线程(或堆栈中的更高层)执行已经在函数中。
由于您的函数是静态的,因此无法访问成员变量,这是使其不可重入的一种方法。如果你有一个维持状态的静态局部变量,那将是另一种使它不可重入的方法。
每次输入时都会创建一个新的MyObject,因此每个执行流程都在处理它自己的MyObject实例,这很好。这意味着他们不会同时尝试访问同一个对象(这将导致竞争条件)。
您在多个呼叫之间共享的唯一内容是控制台本身。如果你在多个线程上调用它们,它们将相互输出到控制台。并且您可能会对同一个文件执行操作(在您的示例中,文件名是硬编码的),但您可能会对多个文件执行操作。如果以前的文件打开文件,连续的线程可能无法打开文件。