Это интересная дилемма писателей библиотек. В моей библиотеке (в моем случае EasyNetQ) Я назначаю локальные ресурсы потока. Поэтому, когда клиент создает новый поток, а затем вызывает определенные методы в моей библиотеке, создаются новые ресурсы. В случае EasyNetQ новый канал для сервера RabbitMQ создается, когда клиент вызывает "Опубликовать в новом потоке". Я хочу иметь возможность обнаруживать, когда клиентский поток завершает работу, чтобы я мог очищать ресурсы (каналы).
Единственный способ сделать это Ive - создать новый поток наблюдателей, который просто блокирует вызов Join в клиентский поток. Здесь простая демонстрация:
Сначала моя библиотека. Он захватывает клиентский поток, а затем создает новый поток, который блокирует "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:
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();
}
}
Когда я запускаю клиент следующим образом:
var client = new Client(new Library());
client.DoWorkInAThread();
// give the client thread time to complete
Thread.Sleep(100);
Я получаю этот вывод:
Library says: StartSomething called
Client thread says: I'm done
Libaray says: Client thread existed
Так оно работает, но это уродливо. Мне действительно не нравится идея всех этих заблокированных потоков наблюдателей, висящих вокруг. Есть ли лучший способ сделать это?
Первая альтернатива.
Предоставить метод, который возвращает работника, который реализует IDisposable, и разъяснить в документации, что вы не должны делиться между рабочими между потоками. Здесь измененная библиотека:
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");
}
}
Клиент теперь немного сложнее:
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. Существующие клиенты должны быть переписаны. Теперь, когда это не так уж плохо, это означало бы пересмотреть их и убедиться, что они правильно очищаются.
Неразрешимая вторая альтернатива. API предоставляет возможность клиенту объявить "область работы". По завершении области библиотека может очистить. Библиотека предоставляет WorkScope, который реализует IDisposable, но в отличие от первой альтернативы выше, метод StartSomething остается в классе Library:
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 в 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();
}
}
Мне нравится это меньше, чем первая альтернатива, потому что это не заставляет пользователя библиотеки думать о сфере видимости.