Threadpool - Как вызвать метод (с параметрами) в основном потоке из рабочего потока
Я работаю через мою первую попытку наложить поток на приложение. Приложение работает с большим набором данных, который разделен на управляемые фрагменты, которые хранятся на диске, поэтому весь набор данных никогда не должен постоянно находиться в памяти. Вместо этого подмножество данных может быть загружено по частям по мере необходимости. Эти куски ранее загружались один за другим в основной поток. Конечно, это эффективно приостановит весь графический интерфейс и другие операции, пока данные не будут полностью загружены.
Поэтому я решил изучить многопоточность и выполнить загрузку, пока приложение продолжает нормально функционировать. Я смог получить базовую концепцию работы с ThreadPool, выполнив что-то в соответствии с псевдокодом ниже:
public class MyApp
{
List<int> listOfIndiciesToBeLoaded; //This list gets updated based on user input
Dictionary<int,Stuff> loadedStuff = new Dictionary<int,Stuff>();
//The main thread queues items to be loaded by the ThreadPool
void QueueUpLoads()
{
foreach(int index in listOfIndiciesToBeLoaded)
{
if(!loadedStuff.ContainsKey(index))
loadedStuff.Add(index,new Stuff());
LoadInfo loadInfo = new LoadInfo(index);
ThreadPool.QueueUserWorkItem(LoadStuff, loadInfo);
}
}
//LoadStuff is called from the worker threads
public void LoadStuff(System.Object loadInfoObject)
{
LoadInfo loadInfo = loadInfoObject as LoadInfo;
int index = loadInfo.index;
int[] loadedValues = LoadValuesAtIndex(index); /* here I do my loading and ...*/
//Then I put the loaded data in the corresponding entry in the dictionary
loadedStuff[index].values = loadedValues;
//Now it is accessible from the main thread and it is flagged as loaded
loadedStuff[index].loaded = true;
}
}
public class Stuff
{
//As an example lets say the data being loaded is an array of ints
int[] values;
bool loaded = false;
}
//a class derived from System.Object to be passed via ThreadPool.QueueUserWorkItem
public class LoadInfo : System.Object
{
public int index;
public LoadInfo(int index)
{
this.index = index;
}
}
Это очень примитивно по сравнению с довольно сложными примерами, с которыми я сталкивался, пытаясь изучить этот материал в последние несколько дней. Конечно, он загружает данные одновременно и помещает их в словарь, доступный из основного потока, но это также оставляет меня с критической проблемой. Мне нужно, чтобы основной поток был уведомлен, когда элемент загружен и какой это элемент, чтобы новые данные могли быть обработаны и отображены. В идеале я хотел бы, чтобы каждая завершенная загрузка вызывала функцию в главном потоке и предоставляла ему индекс и вновь загруженные данные в качестве параметров. Я понимаю, что не могу просто вызывать функции в главном потоке из нескольких других потоков, работающих одновременно. Они должны быть каким-то образом поставлены в очередь, чтобы основной поток запускал их, когда он не выполняет что-то еще. Но это то, где мое текущее понимание связи потоков падает.
Я прочитал несколько подробных объяснений того, как события и делегаты могут быть настроены с помощью Control.Invoke (делегат) при работе с Windows Forms. Но я не работаю с Windows Forms и не смог применить эти идеи. Полагаю, мне нужен более универсальный подход, который не зависит от класса Control. Если вы ответите, пожалуйста, будьте подробны и, возможно, используйте некоторые имена в моем псевдокоде. Так мне будет легче следовать. Потоки, кажется, довольно глубокая тема, и я только начинаю разбираться с основами. Также, пожалуйста, не стесняйтесь вносить предложения о том, как я могу уточнить свой вопрос, чтобы быть более ясным.
1 ответ
Если вы не используете инфраструктуру GUI с каким-либо диспетчером или потоком GUI (например, WPF или WinForms), вам придется делать это вручную.
Один из способов сделать это - использовать SynchronizationContext. Управлять им несколько сложно, но есть несколько статей, в которых рассказывается, как это работает, и как вы можете сделать это самостоятельно:
http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I http://www.codeproject.com/Articles/32113/Understanding-SynchronizationContext-Part-II
Однако я также хотел бы рассмотреть возможность использования одного логического значения DictionaryChanged, которое регулярно проверяется вашим основным потоком (когда он бездействует), чтобы указать, что словарь изменен. Затем флаг можно сбросить в главном потоке, чтобы указать, что это было обработано. Имейте в виду, что вам нужно сделать некоторые блокировки там.
Вы также можете ставить в очередь сообщения, используя потокобезопасную очередь, которая записывается фоновым потоком и считывается из основного потока, если простая переменная не достаточна. Это, по сути, то, что большинство реализаций диспетчера фактически делают под капотом.