У меня есть класс, который использует BlockingCollection следующим образом:
public class Logger : IDisposable
{
private BlockingCollection<LogMessage> _messages = null;
private Thread _worker = null;
private bool _started = false;
public void Start()
{
if (_started) return;
//Some logic to open log file
OpenLogFile();
_messages = new BlockingCollection<LogMessage>(); //int.MaxValue is the default upper-bound
_worker = new Thread(Work) { IsBackground = true };
_worker.Start();
_started = true;
}
public void Stop()
{
if (!_started) return;
// prohibit adding new messages to the queue,
// and cause TryTake to return false when the queue becomes empty.
_messages.CompleteAdding();
// Wait for the consumer thread to finish.
_worker.Join();
//Dispose managed resources
_worker.Dispose();
_messages.Dispose();
//Some logic to close log file
CloseLogFile();
_started = false;
}
/// <summary>
/// Implements IDiposable
/// In this case, it is simply an alias for Stop()
/// </summary>
void IDisposable.Dispose()
{
Stop();
}
/// <summary>
/// This is message consumer thread
/// </summary>
private void Work()
{
LogMessage message;
//Try to get data from queue
while(_messages.TryTake(out message, Timeout.Infinite))
WriteLogMessage(message); //... some simple logic to write 'message'
}
}
Я создаю новый экземпляр класса Logger, вызываю его метод Start(). Затем, если я забуду вызвать метод Dispose, когда экземпляр больше не ссылается, то Worker Thread никогда не закончится. Это своего рода утечка памяти. Я прав? и как преодолеть это?
Вы можете попытаться сохранить только слабую ссылку на BlockingCollection
в рабочем потоке и не ссылаться на объект, ссылающийся на BlockingCollection
. Я сделал статический метод, чтобы убедиться, что мы не ссылаемся на this
экземпляр Logger.
Таким образом, сбор может быть завершен/собран, когда он больше не ссылается. Я не уверен, что это сработает, вы должны попробовать. Это зависит от того, поддерживает ли TryTake коллекцию или нет. Он может не работать в отладке, поскольку GC ведет себя по-разному, поэтому попробуйте в выпуске без отладчика.
public class Logger : IDisposable
{
private BlockingCollection<LogMessage> _messages = null;
private Thread _worker = null;
private bool _started = false;
public void Start()
{
if (_started) return;
//Some logic to open log file
OpenLogFile();
_messages = new BlockingCollection<LogMessage>(); //int.MaxValue is the default upper-bound
_worker = new Thread(Work) { IsBackground = true };
_worker.Start(new WeakReference<BlockingCollection<LogMessage>>(_messages));
_started = true;
}
public void Stop()
{
if (!_started) return;
// prohibit adding new messages to the queue,
// and cause TryTake to return false when the queue becomes empty.
_messages.CompleteAdding();
// Wait for the consumer thread to finish.
_worker.Join();
//Dispose managed resources
_worker.Dispose();
_messages.Dispose();
//Some logic to close log file
CloseLogFile();
_started = false;
}
/// <summary>
/// Implements IDiposable
/// In this case, it is simply an alias for Stop()
/// </summary>
void IDisposable.Dispose()
{
Stop();
}
/// <summary>
/// This is message consumer thread
/// </summary>
private static void Work(object state)
{
LogMessage message;
//Try to get data from queue
do
{
BlockingCollection<LogMessage> messages;
if (((WeakReference<BlockingCollection<LogMessage>>)state).TryGetTarget(out messages)
&& messages.TryTake(out message, Timeout.Infinite))
{
WriteLogMessage(message); //... some simple logic to write 'message'
continue;
}
} while (false);
}
}