【发布时间】:2011-07-12 19:03:55
【问题描述】:
如何在调用异步函数的同一线程中执行回调方法。 调用者线程可能不是 UI 线程...但 UI 不应该挂起..
感谢和问候, 饭菜
【问题讨论】:
标签: multithreading c#-2.0
如何在调用异步函数的同一线程中执行回调方法。 调用者线程可能不是 UI 线程...但 UI 不应该挂起..
感谢和问候, 饭菜
【问题讨论】:
标签: multithreading c#-2.0
没有什么灵丹妙药可以让一个线程在另一个线程上启动委托的执行。目标线程必须专门构造以允许这样做。在 UI 线程的情况下,有一个消息泵来分派和处理消息。此消息泵可用于通过ISynchronizeInvoke 接口执行封送操作。
ISynchronizeInvoke target = someForm; // where someForm is a Form or Control
target.Invoke(
(Action)(() =>
{
MessageBox.Show("I am on the target thread");
}), null);
在您的情况下,调用异步函数的线程必须具有某种内置的生产者-消费者机制,以便在工作线程指示它执行此操作后,在该线程上异步执行回调。不幸的是,这不是一个容易解决的问题。
这是创建一个可以接受要执行的委托的线程的一种方法。
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); }
}
}
}
可以这样使用。
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);
更新:
根据下面的评论 BlockingCollection 仅在 .NET 4.0 中可用或作为 Reactive Extensions 下载的一部分。如果这个数据结构对你不可用,那么这个已经很困难的代码就变得更加困难了。
【讨论】:
CustomThread 不会调用目标线程中的代码。这是因为您没有使用SynchronizationContext... 来证明这一点。在 Windows 窗体上运行测试并将Console.WriteLine 替换为textBox1.Text = "some data";,您将遇到跨线程异常。另外请注意,您使用的是ConcurrentCollection,其中问题标签为C# 3.0
CustomThread,它确实将委托编组到那个线程。我的观点之一是要演示让它与非 UI 线程一起工作涉及多少,因为 OP 确实声明非 UI 线程可能是目标。发生异常是因为您尝试从托管线程以外的线程访问Control,但这仅证明目标线程是非 UI 线程。如果您希望目标线程成为 UI 线程,您显然会改用 Control.Invoke。不过 C# 3.0 的公平点:)
Console.WriteLine("Thread Id: \"{0}\"", Thread.CurrentThread.ManagedThreadId); CustomThread thread = new CustomThread(); thread.Invoke( (Action)(() => { Console.WriteLine("Thread Id: \"{0}\"", Thread.CurrentThread.ManagedThreadId); }), null); 输出将类似于:Thread Id: 9 Thread Id: 10
ISynchronizeInvoke 接受委托注入。
SynchronizationContext 可能有些混淆。并非所有线程都有一个。事实上,大多数都没有。您必须实际继承SynchronizationContext,然后将其附加到目标线程,然后才能从SynchronizationContext.Current 中提取它。我更新了答案以提供目标线程上的上下文,这很容易,因为我已经准备好让目标线程接受委托注入的管道。
使用 BackgroundWorker。回调将在所属线程上。
如果异步操作需要在回调之后继续运行,您可以使用 WPF System.Windows.Application.Current.Dispatcher.Invoke/BeginInvoke 进行多个回调...或者如果是 WinForms,您可以使用窗体或控件实例本身并调用 Invoke/BeginInvoke。
【讨论】:
就像提到的Brian Gideon 一样,您应该使用ISynchronizeInvoke“System.ComponentModel.ISynchronizeInvoke”。在要在另一个线程上编组其线程执行的类上实现它。这里的例子Mediaclass“我实现的一些类正在与Com对象交互,所以它应该在主线程中执行它的方法”;由于它使用System.Threading.SynchronizationContext.Current 的类实现,因此您可以在 WindowsForms 中使用它,但不能在控制台应用程序中使用它,因为 System.Threading.SynchronizationContext.Current 为空。
当您想将此类的执行编组到创建它的线程中时,只需调用其Invoke 方法即可。
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
}
【讨论】: