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, которое регулярно проверяется вашим основным потоком (когда он бездействует), чтобы указать, что словарь изменен. Затем флаг можно сбросить в главном потоке, чтобы указать, что это было обработано. Имейте в виду, что вам нужно сделать некоторые блокировки там.

Вы также можете ставить в очередь сообщения, используя потокобезопасную очередь, которая записывается фоновым потоком и считывается из основного потока, если простая переменная не достаточна. Это, по сути, то, что большинство реализаций диспетчера фактически делают под капотом.

Другие вопросы по тегам