locked
Как правильно работать с потоками на C++? RRS feed

  • Вопрос

  • Всем доброго дня!

    Решил в локальном хранилище приложения хранить файлы настроек игры. Получить папку локального хранилища очень просто. Для этого нужно всего лишь вызвать:

    StorageFolder^ folder=ApplicationData::Current->LocalFolder;

    Теперь мне нужно получить файл из этой папки. Для этого нужно сделать следующее:

    folder->GetFileAsync(fileName);

    Операция асинхронная, следовательно функция тут же передаст управление и мы будем дальше двигаться по коду. Но дальнейший код зависит от выполнения текущего кода. То есть двигаться дальше можно только тогда, когда мы успешно получим файл. Решил использовать пространство имён concurrency от Microsoft. Вот как я это сделал:

    bool File::open(const std::wstring& _fileName,bool _isReadOnly)
    {
    	String^ fileName=ref new String(_fileName.c_str());
    	auto task1=concurrency::create_task<IAsyncOperation<StorageFile^>^>(ApplicationData::Current->LocalFolder->GetFileAsync(fileName));
    	task1.wait();
    	try
    	{
    		file=task1.get();
    	}
    	catch(...)
    	{
    		return false;
    	}
    	if(!file) return false;
    
    	auto task2=concurrency::create_task<IAsyncOperation<IRandomAccessStream^>^>(file->OpenAsync(_isReadOnly ? FileAccessMode::Read : FileAccessMode::ReadWrite));
    	task2.wait();
    	try
    	{
    		stream=task2.get();
    	}
    	catch(...)
    	{
    		return false;
    	}
    	if(!stream) return false;
    
    	return true;
    }

    Для того, чтобы текущий поток приостановил свою работу и подождал когда асинхронная операция завершится, я вызываю метод wait() для каждой задачи. Как я прочитал, этот метод может выдать исключение, если его вызывать в главном потоке приложения (то есть главному потоку нельзя останавливаться для того, чтобы своевременно реагировать на пользовательские действия). Поэтому решил функцию вызывать производить в новом потоке (не в главном). Использовал std::thread(), но когда дело доходило до wait(), срабатывало исключение (причём даже вроде бы не до первого wait(), а до второго). Подумал что видимо нужно отказаться от STL в плане потоков и использовать средства платформы. Сделал вот так:

    auto hWorkItem=ref new Windows::System::Threading::WorkItemHandler(
    	[&](Windows::Foundation::IAsyncAction^ _action)
    {
    	ConfigManager* configManager=ConfigManager::getInstance();
    	configManager->load();
    	isCompleteLoad=true;
    });
    Windows::Foundation::IAsyncAction^ action=Windows::System::Threading::ThreadPool::RunAsync(hWorkItem);

    Этот код вызывается в главном потоке один раз и дальше главный поток в цикле обрабатывает сообщения от ОС, рисует сцену загрузки и всё по кругу (второй раз поток не создаётся). Менеджер конфигурации в методе load() делает следующее:

    File file;
    if(!file.open(_type==Type::tInterface ? L"Interface.cfg" : L"Rank.cfg",true)) return false;
    UInt64 fileSize;
    if(!file.getSize(fileSize)) return false;
    std::vector<UInt8> v;
    if(!file.read(v,(UInt32)fileSize)) return false;
    file.close();

    Но дело так и не доходит до третьей строчки. То есть после вызова wait() поток будто замирает и никогда не пробуждается. Главный цикл бесконечно обрабатывает сообщения и рисует сцену. В общем программа работает, но не идёт дальше.

    Видимо я как-то не так выполняю задачи параллельности. Подскажите, как правильно делать? Всё что мне нужно, так это получить данные из файла.

    Да, у задачи есть метод thin(), который позволяет после выполнения операции дать ей следующее задание. Можно эти thin() повызывать целой цепочкой. Но для моего случая это не хорошо, так как я стараюсь раз описать поведение возможных операций по работе с файлом в классе File. И в дальнейшем уже вызывать их не в первичном потоке, но с ожиданием, чтобы создать файл, открыть, считать, записать.

    Вообще заголовок класса по работе с файлом у меня выглядит следующим образом:

    class File final : private NoCopy
    {
    	private:
    		Windows::Storage::StorageFile^ file;
    		Windows::Storage::Streams::IRandomAccessStream^ stream;
    
    	public:
    		File(void);
    		~File(void);
    
    		bool create(const std::wstring& _fileName);
    		bool open(const std::wstring& _fileName,bool _isReadOnly);
    		bool getSize(UInt64& _size);
    		bool read(std::vector<UInt8>& _buffer,UInt32 _size);
    		bool write(const std::vector<UInt8>& _buffer);
    		void close(void);
    };

    Все эти методы будут вызываться из вторичного потока (потому что первичному нельзя спать). Что я сделал не так, что у меня программа выполняется не так как я хочу?

    Кстати, у меня метод close() реализован следующим образом:

    void File::close(void)
    {
    	stream=nullptr;
    	file=nullptr;
    }

    Умнее я ничего не придумал. А как всё таки правильно завершать работу с файлом и когда он был просто получен в StorageFile^, и когда он был открыт в режиме чтения/записи в IRandomAccessStream^?

    Надеюсь Вы мне поможете, так как уже всю игру реализовал и уже неделю мучаюсь с этой загрузкой. Загрузка + сохранение, и уже релиз. Спасибо за внимание!


    24 декабря 2012 г. 9:29

Ответы

Все ответы