开发者

C# Basic Multi-Threading Question: Call Method on Thread A from Thread B (Thread B started from Thread A)

What is the best way to accomplish this: The main thread (Thread A) creates two other threads (Thread B and Thread C). Threads B and C do heavy disk I/O and eventually need to pass in resources they created to Thread A to then call a method in an external DLL file which requires the thread that created it to be called correctly so only Thread A can call it.

The only other time I ever used threads was in a Windows Forms application, and the invoke methods were just what I needed. This program does not use Windows Forms, and as such there are no Control.Invo开发者_运维问答ke methods to use.

I have noticed in my testing that if a variable is created in Thread A, I have no trouble accessing and modifying it from Thread B/C which seems very wrong to me. With Winforms, I was sure it threw errors for trying to access things created on other threads. I know it is unsafe to change things from multiple threads, but I really hoped .NET would forbid it altogether to ensure safe coding. Does .NET do this, and I am just missing the boat, or does it only do it with WinForm apps?

Since it does seemingly allow this, do I do something like an OS would do, create a flag and monitor it from Thread A to see if it changes. If it does, then call the method. Doesnt the event handler essentially do this, so could an event be used somehow called on the main thread?


Typically, this is unnecessary. You can call a method on any object from any thread, and this is a good thing... UI components and some legacy COM components tend to be the only items which must be accessed from a specific thread.

Instead of trying to call a method on a different thread, normally, you'll try to use synchronization (ie: lock(...) and similar) to protect access to the data itself, and make it safe to work with from multiple threads.

The provides a much finer-grained protection for you, which is good for performance. Using Control.Invoke is actually quite expensive, since it uses Windows messaging to flag to the thread that it needs to run.

That being said, it is actually possible to do this, though quite difficult. The "trick" is that you can make an object with it's own thread, and have that object provide a SynchronizationContext. You can then use SynchronizationContext.Post or Send to run methods within that context.

This is very difficult to get right, and there is very little reason to do this, so as I said, I don't recommend it for most scenarios.


"I was sure it threw errors for trying to access things created on other threads." this statement is only valid for properties of controls. Generic variable can be accessed from any thread. Yes, it can cause problems (race conditions) when done incorrectly, but sometimes it is necessary. You should mark this variables as volatile, minimize access to them, use locks or Interlocked class. This is my advices :)


First of all, "Winforms" does not throw errors simply for trying to access data from another thread. That is impossible; there is no way to know which thread originally declared some variable or created some data. What does result in an exception in Winforms is trying to invoke methods on a Control descendant from any thread other than the UI thread - that is a very specific special case. Aside from that, Winforms is just a library running on top of the exact same runtime as any other .NET application and has no special control over how threads are managed.

There is absolutely no conceivable way for the .NET Framework or any other library to "ensure" safe multi-threaded code. Certain concepts help to reduce race conditions and other concurrency errors, such as .NET 4's Concurrent Collections library, immutable data types, and so on, but it is up to you to use them. When you write multi-threaded code, you assume all risks and liabilities associated with it. It is up to you, the programmer, to ensure that your code is thread-safe.

The specific case you are referring to here seems fairly straightforward. I like to use the Thread Pool so I will show you a solution using events; you could also do this with actual Thread objects and use Thread.Join. Assuming we are in thread A here:

void Xyz()
{
    SomeData dataFromB = null;
    ManualResetEvent finishedB = new ManualResetEvent(false);
    ThreadPool.QueueUserWorkItem(s =>
    {
        dataFromB = GetDataFromB();
        finishedB.Set();
    });

    SomeOtherData dataFromC = null;
    ManualResetEvent finishedC = new ManualResetEvent(false);
    ThreadPool.QueueUserWorkItem(s =>
    {
        dataFromC = GetDataFromC();
        finishedC.Set();
    });

    finishedB.WaitOne();
    finishedC.WaitOne();
    finishedB.Dispose();
    finishedC.Dispose();

    CallExternalDLL(dataFromB, dataFromC);
}

This will block until the entire operation is finished. If you are running this directly from a UI thread, you should call this entire method from another worker thread.


From my pov the easiest way for you will be to work with a synchronised queue where B and C will queue items to.
Thread A will then dequeue from the queue and potentially wait until something new is enqueued.
Look at the comment from Doug in this MSDN article as a simple example of such a queue. You could also enqueue delegates to ensure that the code runs in the right thread.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜