问题描述
下面是一个有趣的库作家的困境。在我的图书馆(在我的情况EasyNetQ)我分配线程本地资源。因此,当一个客户端创建一个新的线程,然后调用我的图书馆新资源的某些方法获得创建。在EasyNetQ的情况下,一个新的渠道到的RabbitMQ服务器创建客户端调用时,发布在一个新的线程。我希望能够检测何时客户端线程退出,这样我可以清理资源(信道)。
Here’s an interesting library writer’s dilemma. In my library (in my case EasyNetQ) I’m assigning thread local resources. So when a client creates a new thread and then calls certain methods on my library new resources get created. In the case of EasyNetQ a new channel to the RabbitMQ server is created when the client calls ‘Publish’ on a new thread. I want to be able to detect when the client thread exits so that I can clean up the resources (channels).
这样做,我想出的唯一办法是创建一个新的守望者的线程,仅仅在块加入调用客户端线程。在这里,一个简单的例子:
The only way of doing this I’ve come up with is to create a new ‘watcher’ thread that simply blocks on a Join call to the client thread. Here a simple demonstration:
首先我的图书馆。它抓住了客户端线程,然后创建一个新的线程,块上加入
First my ‘library’. It grabs the client thread and then creates a new thread which blocks on ‘Join’:
public class Library
{
public void StartSomething()
{
Console.WriteLine("Library says: StartSomething called");
var clientThread = Thread.CurrentThread;
var exitMonitorThread = new Thread(() =>
{
clientThread.Join();
Console.WriteLine("Libaray says: Client thread existed");
});
exitMonitorThread.Start();
}
}
下面是一个使用我的库中的客户端。它创建了一个新的线程,然后调用我的图书馆StartSomething方式:
Here’s a client that uses my library. It creates a new thread and then calls my library’s StartSomething method:
public class Client
{
private readonly Library library;
public Client(Library library)
{
this.library = library;
}
public void DoWorkInAThread()
{
var thread = new Thread(() =>
{
library.StartSomething();
Thread.Sleep(10);
Console.WriteLine("Client thread says: I'm done");
});
thread.Start();
}
}
当我运行客户端是这样的:
When I run the client like this:
var client = new Client(new Library());
client.DoWorkInAThread();
// give the client thread time to complete
Thread.Sleep(100);
我得到这样的输出:
I get this output:
Library says: StartSomething called
Client thread says: I'm done
Libaray says: Client thread existed
所以它的工作原理,但它的丑陋。我真的不喜欢所有这些阻塞守望线程的想法游逛。是否有这样做的更好的办法?
So it works, but it's ugly. I really don’t like the idea of all these blocked watcher threads hanging around. Is there a better way of doing this?
第一选择。
提供一个返回实现IDisposable并使其你不应该线程之间共享工作人员的文件中明确工人的方法。下面是修改后的库:
Provide a method that returns a worker that implements IDisposable and make it clear in the documentation that you should not share workers between threads. Here's the modified library:
public class Library
{
public LibraryWorker GetLibraryWorker()
{
return new LibraryWorker();
}
}
public class LibraryWorker : IDisposable
{
public void StartSomething()
{
Console.WriteLine("Library says: StartSomething called");
}
public void Dispose()
{
Console.WriteLine("Library says: I can clean up");
}
}
客户端现在是一个更复杂一点:
The client is now a little more complicated:
public class Client
{
private readonly Library library;
public Client(Library library)
{
this.library = library;
}
public void DoWorkInAThread()
{
var thread = new Thread(() =>
{
using(var worker = library.GetLibraryWorker())
{
worker.StartSomething();
Console.WriteLine("Client thread says: I'm done");
}
});
thread.Start();
}
}
这种变化的主要问题是,这是一个重大更改的API。现有的客户将必须被重新写入。现在,这不是一件坏事,这将意味着重新审视他们,确保他们正确地清理。
The main problem with this change is that it's a breaking change for the API. Existing clients will have to be re-written. Now that's not such a bad thing, it would mean revisiting them and making sure they are cleaning up correctly.
非破第二个选择。该API提供了一种方法为客户申报的工作范围。一旦范围完成,该库可以清理。该库提供了实现IDisposable一个工作范围,但不像上面的第一个替代方案,StartSomething方法停留在图书馆类:
Non-breaking second alternative. The API provides a way for the client to declare 'work scope'. Once the scope completes, the library can clean up. The library provides a WorkScope that implements IDisposable, but unlike the first alternative above, the StartSomething method stays on the Library class:
public class Library
{
public WorkScope GetWorkScope()
{
return new WorkScope();
}
public void StartSomething()
{
Console.WriteLine("Library says: StartSomething called");
}
}
public class WorkScope : IDisposable
{
public void Dispose()
{
Console.WriteLine("Library says: I can clean up");
}
}
客户端只需放StartSomething电话在工作范围...
The client simply puts the StartSomething call in a WorkScope...
public class Client
{
private readonly Library library;
public Client(Library library)
{
this.library = library;
}
public void DoWorkInAThread()
{
var thread = new Thread(() =>
{
using(library.GetWorkScope())
{
library.StartSomething();
Console.WriteLine("Client thread says: I'm done");
}
});
thread.Start();
}
}
我喜欢比第一种选择这个少,因为它不强制图书馆用户考虑范围。
I like this less than the first alternative because it doesn't force the library user to think about scope.
推荐答案
您可以创建一个线程静态监控器有一个终结。当线程是活的,它会保持监视器对象。当THEAD死亡将停止抱着它。后来,当GC踢,它会完成你的显示器。在终结器,你可以提出,将通知您的架构有关客户端线程(观察)死亡的事件。
You can create a thread static monitor that has a finalizer. When the thread is alive, it will hold the monitor object. When the thead dies it will stop holding it. Later, when GC kicks in, it will finalize your monitor. In the finalizer you can raise an event that will inform your framework about the (observed) death of the client thread.
一个样本code可以在这个要点中找到: https://gist.github.com/2587063
A sample code can be found in this gist: https://gist.github.com/2587063
下面是它的一个副本:
public class ThreadMonitor
{
public static event Action<int> Finalized = delegate { };
private readonly int m_threadId = Thread.CurrentThread.ManagedThreadId;
~ThreadMonitor()
{
Finalized(ThreadId);
}
public int ThreadId
{
get { return m_threadId; }
}
}
public static class Test
{
private readonly static ThreadLocal<ThreadMonitor> s_threadMonitor =
new ThreadLocal<ThreadMonitor>(() => new ThreadMonitor());
public static void Main()
{
ThreadMonitor.Finalized += i => Console.WriteLine("thread {0} closed", i);
var thread = new Thread(() =>
{
var threadMonitor = s_threadMonitor.Value;
Console.WriteLine("start work on thread {0}", threadMonitor.ThreadId);
Thread.Sleep(1000);
Console.WriteLine("end work on thread {0}", threadMonitor.ThreadId);
});
thread.Start();
thread.Join();
// wait for GC to collect and finalize everything
GC.GetTotalMemory(forceFullCollection: true);
Console.ReadLine();
}
}
我希望它能帮助。我觉得它比你的额外等待线程更优雅。
I hope it helps. I think it's more elegant than your extra waiting thread.
这篇关于如何检测,当客户端线程退出?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!