Multithreading 从多个线程跟踪对对象的WeakReference
我正在设计一个静态消息总线,允许订阅和发布任意类型的消息。为了避免要求观察员明确取消订阅,我希望跟踪指向代理的WeakReference对象,而不是跟踪代理本身。我最终编写了一些类似于保罗·斯托维尔在他的博客中描述的东西 我的问题是:与Paul的代码相反,我的观察者在一个线程上订阅消息,但消息可能在另一个线程上发布。在本例中,我观察到,当我需要通知观察者时,我的WeakReference.Target值为null,表示目标已被收集,即使我确定它们不是。对于短弱引用和长弱引用,问题仍然存在 相反,当订阅和发布从同一线程完成时,代码工作正常。后者是正确的,即使我最终从ThreadPool枚举了一个新线程上的目标,只要请求最初来自我订阅消息的同一个线程 我知道这是一个非常具体的案例,因此非常感谢您的帮助Multithreading 从多个线程跟踪对对象的WeakReference,multithreading,c#-4.0,delegates,garbage-collection,weak-references,Multithreading,C# 4.0,Delegates,Garbage Collection,Weak References,我正在设计一个静态消息总线,允许订阅和发布任意类型的消息。为了避免要求观察员明确取消订阅,我希望跟踪指向代理的WeakReference对象,而不是跟踪代理本身。我最终编写了一些类似于保罗·斯托维尔在他的博客中描述的东西 我的问题是:与Paul的代码相反,我的观察者在一个线程上订阅消息,但消息可能在另一个线程上发布。在本例中,我观察到,当我需要通知观察者时,我的WeakReference.Target值为null,表示目标已被收集,即使我确定它们不是。对于短弱引用和长弱引用,问题仍然存在 相反,
我的问题是:如果线程同步正确,我是否应该不能从多个线程可靠地访问WeakReference对象?看来我不能,这对我来说没有多大意义。那么,我做得不对的是什么呢?看起来,在将代码简化为一种更简单的形式(见下文)之后,它现在可以正常工作了。这意味着,导致过早收集弱引用目标的问题必须存在于代码的其他地方。因此,为了回答我自己的问题,弱引用似乎可以从多个线程安全地访问 以下是我的测试代码:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting the app");
Test test = new Test();
// uncomment these lines to cause automatic unsubscription from Message1
// test = null;
// GC.Collect();
// GC.WaitForPendingFinalizers();
// publish Message1 on this thread
// MessageBus.Publish<Message1>(new Message1());
// publish Message1 on another thread
ThreadPool.QueueUserWorkItem(delegate
{
MessageBus.Publish<Message1>(new Message1());
});
while (!MessageBus.IamDone)
{
Thread.Sleep(100);
}
Console.WriteLine("Exiting the app");
Console.WriteLine("Press <ENTER> to terminate program.");
Console.WriteLine();
Console.ReadLine();
}
}
public class Test
{
public Test()
{
Console.WriteLine("Subscribing to message 1.");
MessageBus.Subscribe<Message1>(OnMessage1);
Console.WriteLine("Subscribing to message 2.");
MessageBus.Subscribe<Message2>(OnMessage2);
}
public void OnMessage1(Message1 message)
{
Console.WriteLine("Got message 1. Publishing message 2");
MessageBus.Publish<Message2>(new Message2());
}
public void OnMessage2(Message2 message)
{
Console.WriteLine("Got message 2. Closing the app");
MessageBus.IamDone = true;
}
}
public abstract class MessageBase
{
public string Message;
}
public class Message1 : MessageBase
{
}
public class Message2 : MessageBase
{
}
public static class MessageBus
{
// This is here purely for this test
public static bool IamDone = false;
/////////////////////////////////////
/// <summary>
/// A dictionary of lists of handlers of messages by message type
/// </summary>
private static ConcurrentDictionary<string, List<WeakReference>> handlersDict = new ConcurrentDictionary<string, List<WeakReference>>();
/// <summary>
/// Thread synchronization object to use with Publish calls
/// </summary>
private static object _lockPublishing = new object();
/// <summary>
/// Thread synchronization object to use with Subscribe calls
/// </summary>
private static object _lockSubscribing = new object();
/// <summary>
/// Creates a work queue item that encapsulates the provided parameterized message
/// and dispatches it.
/// </summary>
/// <typeparam name="TMessage">Message argument type</typeparam>
/// <param name="message">Message argument</param>
public static void Publish<TMessage>(TMessage message)
where TMessage : MessageBase
{
// create the dictionary key
string key = String.Empty;
key = typeof(TMessage).ToString();
// initialize a queue work item argument as a tuple of the dictionary type key and the message argument
Tuple<string, TMessage, Exception> argument = new Tuple<string, TMessage, Exception>(key, message, null);
// push the message on the worker queue
ThreadPool.QueueUserWorkItem(new WaitCallback(_PublishMessage<TMessage>), argument);
}
/// <summary>
/// Publishes a message to the bus, causing observers to be invoked if appropriate.
/// </summary>
/// <typeparam name="TArg">Message argument type</typeparam>
/// <param name="stateInfo">Queue work item argument</param>
private static void _PublishMessage<TArg>(Object stateInfo)
where TArg : class
{
try
{
// translate the queue work item argument to extract the message type info and
// any arguments
Tuple<string, TArg, Exception> arg = (Tuple<string, TArg, Exception>)stateInfo;
// call all observers that have registered to receive this message type in parallel
Parallel.ForEach(handlersDict.Keys
// find the right dictionary list entry by message type identifier
.Where(handlerKey => handlerKey == arg.Item1)
// dereference the list entry by message type identifier to get a reference to the observer
.Select(handlerKey => handlersDict[handlerKey]), (handlerList, state) =>
{
lock (_lockPublishing)
{
List<int> descopedRefIndexes = new List<int>(handlerList.Count);
// search the list of references and invoke registered observers
foreach (WeakReference weakRef in handlerList)
{
// try to obtain a strong reference to the target
Delegate dlgRef = (weakRef.Target as Delegate);
// check if the underlying delegate reference is still valid
if (dlgRef != null)
{
// yes it is, get the delegate reference via Target property, convert it to Action and invoke the observer
try
{
(dlgRef as Action<TArg>).Invoke(arg.Item2);
}
catch (Exception e)
{
// trouble invoking the target observer's reference, mark it for deletion
descopedRefIndexes.Add(handlerList.IndexOf(weakRef));
Console.WriteLine(String.Format("Error looking up target reference: {0}", e.Message));
}
}
else
{
// the target observer's reference has been descoped, mark it for deletion
descopedRefIndexes.Add(handlerList.IndexOf(weakRef));
Console.WriteLine(String.Format("Message type \"{0}\" has been unsubscribed from.", arg.Item1));
MessageBus.IamDone = true;
}
}
// remove any descoped references
descopedRefIndexes.ForEach(index => handlerList.RemoveAt(index));
}
});
}
// catch all Exceptions
catch (AggregateException e)
{
Console.WriteLine(String.Format("Error dispatching messages: {0}", e.Message));
}
}
/// <summary>
/// Subscribes the specified delegate to handle messages of type TMessage
/// </summary>
/// <typeparam name="TArg">Message argument type</typeparam>
/// <param name="action">WeakReference that represents the handler for this message type to be registered with the bus</param>
public static void Subscribe<TArg>(Action<TArg> action)
where TArg : class
{
// validate input
if (action == null)
throw new ArgumentNullException(String.Format("Error subscribing to message type \"{0}\": Specified action reference is null.", typeof(TArg)));
// build the queue work item key identifier
string key = typeof(TArg).ToString();
// check if a message of this type was already added to the bus
if (!handlersDict.ContainsKey(key))
{
// no, it was not, create a new dictionary entry and add the new observer's reference to it
List<WeakReference> newHandlerList = new List<WeakReference>();
handlersDict.TryAdd(key, newHandlerList);
}
lock (_lockSubscribing)
{
// append this new observer's reference to the list, if it does not exist already
if (!handlersDict[key].Any(existing => (existing.Target as Delegate) != null && (existing.Target as Delegate).Equals(action)))
{
// append the new reference
handlersDict[key].Add(new WeakReference(action, true));
}
}
}
}
}
使用系统;
使用System.Collections.Concurrent;
使用System.Collections.Generic;
使用System.Linq;
使用System.Linq.Expressions;
使用系统线程;
使用System.Threading.Tasks;
名称空间测试
{
班级计划
{
静态void Main(字符串[]参数)
{
Console.WriteLine(“启动应用程序”);
测试=新测试();
//取消对这些行的注释会导致自动取消订阅Message1
//test=null;
//GC.Collect();
//GC.WaitForPendingFinalizers();
//在此线程上发布Message1
//Publish(newmessage1());
//在另一个线程上发布Message1
ThreadPool.QueueUserWorkItem(委托
{
Publish(newmessage1());
});
而(!MessageBus.IamDone)
{
睡眠(100);
}
Console.WriteLine(“退出应用程序”);
Console.WriteLine(“按下以终止程序”);
Console.WriteLine();
Console.ReadLine();
}
}
公开课考试
{
公开考试()
{
Console.WriteLine(“订阅消息1”);
订阅(OnMessage1);
Console.WriteLine(“订阅消息2”);
订阅(OnMessage2);
}
消息1上的公共无效(消息1消息)
{
Console.WriteLine(“获取消息1.发布消息2”);
Publish(newmessage2());
}
消息2上的公共无效(消息2消息)
{
Console.WriteLine(“获取消息2.关闭应用程序”);
MessageBus.IamDone=true;
}
}
公共抽象类消息库
{
公共字符串消息;
}
公共类Message1:MessageBase
{
}
公共类Message2:MessageBase
{
}
公共静态类消息总线
{
//这纯粹是为了这个测试
公共静态bool IamDone=false;
/////////////////////////////////////
///
///按消息类型列出消息处理程序的字典
///
私有静态ConcurrentDictionary handlersDict=新ConcurrentDictionary();
///
///用于发布调用的线程同步对象
///
私有静态对象_lockPublishing=新对象();
///
///用于订阅调用的线程同步对象
///
私有静态对象_locksubling=新对象();
///
///创建封装提供的参数化消息的工作队列项
///并发送它。
///
///消息参数类型
///消息参数
公共静态无效发布(TMessage消息)
其中TMessage:MessageBase
{
//创建字典键
string key=string.Empty;
key=typeof(TMessage).ToString();
//将队列工作项参数初始化为字典类型键和消息参数的元组
元组参数=新元组(键、消息、空);
//将消息推送到工作队列上
QueueUserWorkItem(新的WaitCallback(_PublishMessage),参数);
}
///
///将消息发布到总线,从而在适当时调用观察器。
///
///消息参数类型
///队列工作项参数
私有静态void_PublishMessage(对象状态信息)
TArg:在哪里上课
{
尝试
{
//转换队列工作项参数以提取消息类型信息和
//有什么论据吗
元组arg=(元组)stateInfo;
//调用所有已注册以并行接收此消息类型的观察者
Parallel.ForEach(把手钥匙
//按消息类型标识符查找正确的字典列表项
public class MessageBus : Singleton<MessageBus> // Singleton<> is my library class
public static class MessageBus