Thread switching
How to execute the 开发者_开发问答callback method in same thread which calls Asynchronous function. the caller thread may not be UI Thread... But UI Should not hang..
Thanks & Regards, Dinesh
There is no magic bullet that will allow one thread to initiate the execution of a delegate onto another thread. The target thread must be specially constructed to allow this. In the case of the UI thread there is a message pump that dispatches and processes messages. This message pump can be used to perform the marshaling operation via the ISynchronizeInvoke
interface.
ISynchronizeInvoke target = someForm; // where someForm is a Form or Control
target.Invoke(
(Action)(() =>
{
MessageBox.Show("I am on the target thread");
}), null);
In your case the thread calling the asynchronous function must have some kind of producer-consumer mechanism built into it to get a callback to execute asynchronously on that thread after it has been instructed to do so from the worker thread. Unfortunately, this is not a trivial problem to solve.
Here is one way you can create a thread that can accept delegates to be executed.
public class SynchronizeInvokeThread : ISynchronizeInvoke
{
private Thread m_Thread;
private BlockingCollection<WorkItem> m_Collection = new BlockingCollection<WorkItem>();
public SynchronizeInvokeThread()
{
m_Thread = new Thread(
() =>
{
SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext(this));
while (true)
{
WorkItem wi = m_Collection.Take();
wi.Complete(wi.Method.DynamicInvoke(wi.Args));
}
});
m_Thread.Start();
}
public IAsyncResult BeginInvoke(Delegate method, object[] args)
{
var wi = new WorkItem(method, args);
m_Collection.Add(wi);
return wi;
}
public object EndInvoke(IAsyncResult result)
{
var wi = (WorkItem)result;
wi.AsyncWaitHandle.WaitOne();
return wi.Result;
}
public object Invoke(Delegate method, object[] args)
{
var wi = new WorkItem(method, args);
m_Collection.Add(wi);
wi.AsyncWaitHandle.WaitOne();
return wi.Result;
}
public bool InvokeRequired
{
get { return Thread.CurrentThread != m_Thread; }
}
private class MySynchronizationContext : SynchronizationContext
{
private ISynchronizeInvoke m_SynchronizingObject;
public MySynchronizationContext(ISynchronizeInvoke synchronizingObject)
{
m_SynchronizingObject = synchronizingObject;
}
public override void Post(SendOrPostCallback d, object state)
{
m_SynchronizingObject.BeginInvoke(d, new object[] { state });
}
public override void Send(SendOrPostCallback d, object state)
{
m_SynchronizingObject.Invoke(d, new object[] { state });
}
}
private class WorkItem : IAsyncResult
{
private Delegate m_Method;
private object[] m_Args;
private object m_Result = null;
private ManualResetEvent m_Signal = new ManualResetEvent(false);
public WorkItem(Delegate method, object[] args)
{
m_Method = method;
m_Args = args;
}
public void Complete(object result)
{
m_Result = result;
m_Signal.Set();
}
public object Result
{
get { return m_Result; }
}
public Delegate Method
{
get { return m_Method; }
}
public object[] Args
{
get { return m_Args; }
}
public object AsyncState
{
get { return null; }
}
public WaitHandle AsyncWaitHandle
{
get { return m_Signal; }
}
public bool CompletedSynchronously
{
get { return false; }
}
public bool IsCompleted
{
get { return m_Signal.WaitOne(0); }
}
}
}
It can be used like this.
ISynchronizeInvoke target = new SynchronizeInvokeThread();
target.Invoke(
(Action)(() =>
{
Console.WriteLine("I am on the target thread");
SynchronizationContext.Current.Post(
(state) =>
{
Console.WriteLine("I even have a synchronization context!");
}, null);
}), null);
Update:
Per the comment below BlockingCollection
is only available in .NET 4.0 or as part of the Reactive Extensions download. If this data structure is not available to you then this already difficult code just became even harder.
Use a BackgroundWorker. The callback will be on the owning thread.
If the asynchronous operation needs to be on-running even after the callback, you can make multiple callbacks using the WPF System.Windows.Application.Current.Dispatcher.Invoke/BeginInvoke...or if WinForms, you can use the form or control instance itself and call Invoke/BeginInvoke.
Like Brian Gideon
mentioned out you should use ISynchronizeInvoke "System.ComponentModel.ISynchronizeInvoke". Implement it on the class that you want to marshal its thread execution on another thread. Here example Media
class "some class I implemented was interacting with Com object so it should execute its methods in the main thread"; because of the class implementation it uses System.Threading.SynchronizationContext.Current
so you can use it in WindowsForms but not a Console application because the System.Threading.SynchronizationContext.Current is null.
Whenever you want to marshal the execution of this class into the thread that created it just call its Invoke
method.
public abstract class Media : ISynchronizeInvoke
{
//....
private readonly System.Threading.SynchronizationContext _currentContext = System.Threading.SynchronizationContext.Current;
private readonly System.Threading.Thread _mainThread = System.Threading.Thread.CurrentThread;
private readonly object _invokeLocker = new object();
//....
#region ISynchronizeInvoke Members
public bool InvokeRequired
{
get
{
return System.Threading.Thread.CurrentThread.ManagedThreadId != this._mainThread.ManagedThreadId;
}
}
/// <summary>
/// This method is not supported!
/// </summary>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
[Obsolete("This method is not supported!", true)]
public IAsyncResult BeginInvoke(Delegate method, object[] args)
{
throw new NotSupportedException("The method or operation is not implemented.");
}
/// <summary>
/// This method is not supported!
/// </summary>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
[Obsolete("This method is not supported!", true)]
public object EndInvoke(IAsyncResult result)
{
throw new NotSupportedException("The method or operation is not implemented.");
}
public object Invoke(Delegate method, object[] args)
{
if (method == null)
{
throw new ArgumentNullException("method");
}
lock (_invokeLocker)
{
object objectToGet = null;
SendOrPostCallback invoker = new SendOrPostCallback(
delegate(object data)
{
objectToGet = method.DynamicInvoke(args);
});
_currentContext.Send(new SendOrPostCallback(invoker), method.Target);
return objectToGet;
}
}
public object Invoke(Delegate method)
{
return Invoke(method, null);
}
#endregion//ISynchronizeInvoke Members
}
精彩评论