Patrick Skelton Ответов: 4

Как читать файл асинхронно при запуске приложения?


У меня есть странная проблема с FileStream.ReadAsync() Я вызываю функцию, которая выполняет асинхронное чтение из потока из моего основного приложения OnStartup() Когда я читаю асинхронно, приложение никогда не отображает свое главное окно. Если я просто изменю чтение на синхронную версию, приложение запустится.

Это мой OnStartup() переопределение:

protected override async void OnStartup( StartupEventArgs e )
{
	base.OnStartup( e );
	Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
	LogsEmailer logsEmailer = new LogsEmailer();
	string testString = await logsEmailer.TestReadFileAsync( Path.Combine( logsFolder, "TestFile.txt" ) );
	StartupUri = new Uri( "./View/MainWindow.xaml", UriKind.Relative );
}


Приложение никогда не запускается с этой функцией:

public async Task<string> TestReadFileAsync( string filePath )
{
	using( FileStream sourceStream = File.OpenRead( filePath ) )
	{
		byte[] copyBuffer = new byte[ 4096 ];
		bool doneCopying = false;
		while( !doneCopying )
		{
			int numSourceBytes = await sourceStream.ReadAsync( copyBuffer, 0, copyBuffer.Length );
			if( numSourceBytes == 0 )
				doneCopying = true;
		}
	}
	return "test string";
}


Но все начинается именно с этого:

public async Task<string> TestReadFileAsync( string filePath )
{
	using( FileStream sourceStream = File.OpenRead( filePath ) )
	{
		byte[] copyBuffer = new byte[ 4096 ];
		bool doneCopying = false;
		while( !doneCopying )
		{
			int numSourceBytes = sourceStream.Read( copyBuffer, 0, copyBuffer.Length );
			if( numSourceBytes == 0 )
				doneCopying = true;
		}
	}
	return "test string";
}


Очевидно, что я получаю предупреждение со вторым фрагментом кода, который говорит, что он будет завершен синхронно. И когда я говорю, что вторая версия работает, я имею в виду только то, что она позволяет приложению запускаться; я не могу использовать ее, потому что операция, которую я пытаюсь выполнить, занимает слишком много времени, чтобы быть выполненной синхронно. OnStartup().

Наверное, я упускаю здесь что-то глупое. Кто-нибудь может мне сказать, что именно?

Что я уже пробовал:

Я уже упоминал выше, я попытался удалить асинхронность из потока чтения, и приложение запускается нормально.

Я только что обнаружил, что если я изменю свой OnStartup() переопределение должно быть следующим, после чего приложение запускается нормально.

protected override async void OnStartup( StartupEventArgs e )
{
	base.OnStartup( e );
	Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
	StartupUri = new Uri( "./View/MainWindow.xaml", UriKind.Relative );
	LogsEmailer logsEmailer = new LogsEmailer();
	string testString = await logEmailer.TestReadFileAsync( Path.Combine( logsFolder, "TestFile.txt" ) );
}

phil.o

Итак, с тем, что вы только что обнаружили, можете ли Вы наконец увидеть что-то входящее в вашу переменную testString (когда вы отлаживаете свое приложение и помещаете точку останова где-то в своем переопределении)?
Кстати, хороший вопрос, я его поддержал.

Patrick Skelton

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

BillWoodruff

Это приложение WinForm ?

4 Ответов

Рейтинг:
34

Richard Deeming

Это одна из самых больших проблем с async void методы - метод возвращается до того, как он был завершен, и вызывающий не имеет никакого способа узнать, что метод все еще выполняет работу.

То Application класс поднимает Startup событие. Когда обработчик возвращается - в первый раз await позвоните в твоем примере - то это называет собственный DoStartup метод. Этот метод считывает StartupUri, который вы код еще не установили, так что это не имеет никакого отношения.

Как вы обнаружили, если вы переместите линию, которая назначает StartupUri до первый await звоните, тогда все будет работать как положено.

В идеале, вы должны переместить async код для метода перенастройки задачи. Затем вы можете сохранить эту задачу в поле, а более поздний код может await это для того, чтобы она была завершена.

Избегайте асинхронных методов void | Вы были взломаны[^]


Рейтинг:
24

Dirk Bahle

Я не уверен в вашем коде, но я думаю, что вы делаете это на ранней стадии запуска приложения.

Если у вас есть окно для отображения со статусом, почему бы не позволить окну загрузиться первым и не выполнить асинхронный вызов с отображением прогресса? Я реализовал несколько демо-версий, которые показывают, как это может работать:

См., например:, MainWindow.cs -> загруженное событие в FilterTreeView.zip демо-версия:

Расширенные древовидные представления WPF в C#/VB.Net Часть 3 из n[^]

Популярность: дополнительно в WPF TreeView в C#или VB.Чистая часть 3 из Н[^]


Рейтинг:
2

George Swan

- Вы правы. Я думал, что ваше главное окно зависит от загружаемого текстового файла.


Patrick Skelton

Извините, я должен был выразиться яснее.

Рейтинг:
1

George Swan

Попробуйте загрузить окно в качестве ресурса в части продолжения асинхронного метода.


protected override async void OnStartup( StartupEventArgs e )
{
	base.OnStartup( e );
	Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
	LogsEmailer logsEmailer = new LogsEmailer();
        //StartupUri = new Uri( "./View/MainWindow.xaml", UriKind.Relative );
	string testString = await logsEmailer.TestReadFileAsync( Path.Combine( logsFolder, "TestFile.txt" ) );
	Uri startUpUri = new Uri("MainWindow.xaml", UriKind.Relative);
        MainWindow=LoadComponent(startUpUri)as Window;
        MainWindow.Show();
}


Patrick Skelton

Я не пробовал этого (потому что я временно без среды разработки - другая история), но разве это не будет более или менее то же самое, что делать мою файловую работу синхронно? Под этим я подразумеваю, что главное окно не будет загружаться до тех пор, пока работа с файлом (что может занять некоторое время) не завершится?

Должен признаться, что мне нужно освежить свою память о продолжении задачи, так что здесь я могу быть совершенно не прав.