如果在异步方法中使用,为什么 GetManifestResourceStream 返回 null?
Why is GetManifestResourceStream returning null if used inside an async Method?
在我的程序中,我正在将资源文件写入用户选择的某个位置。为此,我正在使用 GetManifestResourceStream。一切正常。
接下来我想让我的写操作不阻塞UI。所以我使用异步等待更改了代码。
不幸的是,GetManifestRresourceStream 现在返回 null。在不使用异步等待的情况下改回一切正常。
我做错了什么,我该如何解决才能复制文件并且 UI 不会被阻止。
这是没有 async/await 的工作方式(请不要被方法名称混淆)
private void InstallButton_Click(object sender, RoutedEventArgs e)
{
InstallProgrammAsyc();
}
private void InstallProgrammAsyc()
{
try
{
FinalMessage = "";
PreInstallationBlock.Visibility = Visibility.Collapsed;
DuringInstallationBlock.Visibility = Visibility.Visible;
CopyFileToDestinationAsync("MyNameSpace", InstallPath, "Resources", "some.exe");
PrepareProgrammForFinish();
}
catch (Exception ex)
{
DuringInstallationBlock.Visibility = Visibility.Collapsed;
AfterInstallationBlock.Visibility = Visibility.Visible;
FinalMessage = $"Unexpected Error occured. Please try again. {ex.Message}";
}
}
private void CopyFileToDestinationAsync(string nameSpace,string outDirectory, string internalPath, string resourceName)
{
Assembly assembly = Assembly.GetCallingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(nameSpace + "." + (internalPath == "" ? "" : internalPath + ".") + resourceName))
{
using( BinaryReader br = new BinaryReader(stream))
{
using(FileStream fs = new FileStream(outDirectory + "\" + resourceName, FileMode.Create))
{
using (BinaryWriter bw = new BinaryWriter(fs))
{
// await Task.Run( ()=> bw.Write(br.ReadBytes((int)stream.Length)));
bw.Write(br.ReadBytes((int)stream.Length));
}
}
}
}
Thread.Sleep(2000);
//For User Friendliness wait 2 seconds to finish
// await Task.Run(() => Thread.Sleep(2000));
}
----------
这就是我用 async/await
尝试的方式
private async Task CopyFileToDestinationAsync(string nameSpace,string outDirectory, string internalPath, string resourceName)
{
Assembly assembly = Assembly.GetCallingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(nameSpace + "." + (internalPath == "" ? "" : internalPath + ".") + resourceName))
{
using( BinaryReader br = new BinaryReader(stream))
{
using(FileStream fs = new FileStream(outDirectory + "\" + resourceName, FileMode.Create))
{
using (BinaryWriter bw = new BinaryWriter(fs))
{
await Task.Run( ()=> bw.Write(br.ReadBytes((int)stream.Length)));
// bw.Write(br.ReadBytes((int)stream.Length));
}
}
}
}
//Thread.Sleep(2000);
//For User Friendliness wait 2 seconds to finish
await Task.Run(() => Thread.Sleep(2000));
}
private void FinishButton_Click(object sender, RoutedEventArgs e)
{
System.Windows.Application.Current.Shutdown();
}
private async void InstallProgrammAsyc()
{
try
{
FinalMessage = "";
PreInstallationBlock.Visibility = Visibility.Collapsed;
DuringInstallationBlock.Visibility = Visibility.Visible;
await CopyFileToDestinationAsync("MyNameSpace", InstallPath, "Resources", "some.exe");
PrepareProgrammForFinish();
}
catch (Exception ex)
{
DuringInstallationBlock.Visibility = Visibility.Collapsed;
AfterInstallationBlock.Visibility = Visibility.Visible;
FinalMessage = $"Unexpected Error occured. Please try again. {ex.Message}";
}
}
private void InstallButton_Click(object sender, RoutedEventArgs e)
{
InstallProgrammAsyc();
}
由于异步的工作方式,这是一个有趣的边缘案例。
让我们采用那些测试方法:
private void Button_Click(object sender, RoutedEventArgs e)
{
ShowAssembly();
ShowAssemblyAsync();
}
private void ShowAssembly()
{
Assembly assembly = Assembly.GetCallingAssembly();
MessageBox.Show(assembly.FullName);
}
private async void ShowAssemblyAsync()
{
Assembly assembly = Assembly.GetCallingAssembly();
MessageBox.Show(assembly.FullName);
}
在 ShowAssembly
中,它将显示程序的主程序集的名称。在 ShowAssemblyAsync
中,它将显示 "mscorlib"。怎么回事?
如果您在 ShowAssembly
和 ShowAssemblyAsync
中放置断点 Visual Studio,您将获得以下调用堆栈:
DesktopClient.exe!DesktopClient.MainWindow.ShowAssembly() Line 119 C#
DesktopClient.exe!DesktopClient.MainWindow.Button_Click(object sender, System.Windows.RoutedEventArgs e) Line 109 C#
DesktopClient.exe!DesktopClient.MainWindow.ShowAssemblyAsync() Line 128 C#
DesktopClient.exe!DesktopClient.MainWindow.Button_Click(object sender, System.Windows.RoutedEventArgs e) Line 110 C#
没什么可疑的。然而,Visual Studio 是在骗你。如果我改为使用较低级别的调试器(在本例中为 WinDbg),我会得到以下调用堆栈:
00efe58c 07cf423b DesktopClient.MainWindow.ShowAssembly()
00efe5a8 07cf41dc DesktopClient.MainWindow.Button_Click(System.Object, System.Windows.RoutedEventArgs)
00efe4d8 07cf43c6 DesktopClient.MainWindow+d__10.MoveNext()
00efe51c 5ddaa48d System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Start[[System.__Canon, mscorlib]](System.__Canon ByRef)
00efe574 07cf4317 DesktopClient.MainWindow.ShowAssemblyAsync()
00efe5a8 07cf41e6 DesktopClient.MainWindow.ButtonPlus_Click(System.Object, System.Windows.RoutedEventArgs)
您可以看到来自 ShowAssemblyAsync
的调用堆栈在应用 async 关键字后已完全更改(Visual Studio 隐藏它以使调试更容易)。一个后果是该方法现在由 System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Start
调用,这就是我们在检索调用程序集时得到 "mscorlib" 的原因。
作为修复,您可以使用 Assembly.GetExecutingAssembly
(假设您的 CopyFileToDestinationAsync
方法位于正确的程序集中)或明确定位程序集:typeof(MainWindow).Assembly
。或者,就像您所做的那样,在调用方法之前检索流。
另一种方法是引入一个额外的非异步方法,以便在调用真正的异步实现之前检索程序集:
private Task CopyFileToDestinationAsync(string nameSpace,string outDirectory, string internalPath, string resourceName)
{
Assembly assembly = Assembly.GetCallingAssembly();
return CopyFileToDestinationAsync(assembly, nameSpace, outDirectory, internalPath, resourceName);
}
private async Task CopyFileToDestinationAsync(Assembly assembly string nameSpace,string outDirectory, string internalPath, string resourceName)
{
using (Stream stream = assembly.GetManifestResourceStream(nameSpace + "." + (internalPath == "" ? "" : internalPath + ".") + resourceName))
{
// ...
}
}
在我的程序中,我正在将资源文件写入用户选择的某个位置。为此,我正在使用 GetManifestResourceStream。一切正常。
接下来我想让我的写操作不阻塞UI。所以我使用异步等待更改了代码。 不幸的是,GetManifestRresourceStream 现在返回 null。在不使用异步等待的情况下改回一切正常。
我做错了什么,我该如何解决才能复制文件并且 UI 不会被阻止。
这是没有 async/await 的工作方式(请不要被方法名称混淆)
private void InstallButton_Click(object sender, RoutedEventArgs e)
{
InstallProgrammAsyc();
}
private void InstallProgrammAsyc()
{
try
{
FinalMessage = "";
PreInstallationBlock.Visibility = Visibility.Collapsed;
DuringInstallationBlock.Visibility = Visibility.Visible;
CopyFileToDestinationAsync("MyNameSpace", InstallPath, "Resources", "some.exe");
PrepareProgrammForFinish();
}
catch (Exception ex)
{
DuringInstallationBlock.Visibility = Visibility.Collapsed;
AfterInstallationBlock.Visibility = Visibility.Visible;
FinalMessage = $"Unexpected Error occured. Please try again. {ex.Message}";
}
}
private void CopyFileToDestinationAsync(string nameSpace,string outDirectory, string internalPath, string resourceName)
{
Assembly assembly = Assembly.GetCallingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(nameSpace + "." + (internalPath == "" ? "" : internalPath + ".") + resourceName))
{
using( BinaryReader br = new BinaryReader(stream))
{
using(FileStream fs = new FileStream(outDirectory + "\" + resourceName, FileMode.Create))
{
using (BinaryWriter bw = new BinaryWriter(fs))
{
// await Task.Run( ()=> bw.Write(br.ReadBytes((int)stream.Length)));
bw.Write(br.ReadBytes((int)stream.Length));
}
}
}
}
Thread.Sleep(2000);
//For User Friendliness wait 2 seconds to finish
// await Task.Run(() => Thread.Sleep(2000));
}
----------
这就是我用 async/await
尝试的方式
private async Task CopyFileToDestinationAsync(string nameSpace,string outDirectory, string internalPath, string resourceName)
{
Assembly assembly = Assembly.GetCallingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(nameSpace + "." + (internalPath == "" ? "" : internalPath + ".") + resourceName))
{
using( BinaryReader br = new BinaryReader(stream))
{
using(FileStream fs = new FileStream(outDirectory + "\" + resourceName, FileMode.Create))
{
using (BinaryWriter bw = new BinaryWriter(fs))
{
await Task.Run( ()=> bw.Write(br.ReadBytes((int)stream.Length)));
// bw.Write(br.ReadBytes((int)stream.Length));
}
}
}
}
//Thread.Sleep(2000);
//For User Friendliness wait 2 seconds to finish
await Task.Run(() => Thread.Sleep(2000));
}
private void FinishButton_Click(object sender, RoutedEventArgs e)
{
System.Windows.Application.Current.Shutdown();
}
private async void InstallProgrammAsyc()
{
try
{
FinalMessage = "";
PreInstallationBlock.Visibility = Visibility.Collapsed;
DuringInstallationBlock.Visibility = Visibility.Visible;
await CopyFileToDestinationAsync("MyNameSpace", InstallPath, "Resources", "some.exe");
PrepareProgrammForFinish();
}
catch (Exception ex)
{
DuringInstallationBlock.Visibility = Visibility.Collapsed;
AfterInstallationBlock.Visibility = Visibility.Visible;
FinalMessage = $"Unexpected Error occured. Please try again. {ex.Message}";
}
}
private void InstallButton_Click(object sender, RoutedEventArgs e)
{
InstallProgrammAsyc();
}
由于异步的工作方式,这是一个有趣的边缘案例。
让我们采用那些测试方法:
private void Button_Click(object sender, RoutedEventArgs e)
{
ShowAssembly();
ShowAssemblyAsync();
}
private void ShowAssembly()
{
Assembly assembly = Assembly.GetCallingAssembly();
MessageBox.Show(assembly.FullName);
}
private async void ShowAssemblyAsync()
{
Assembly assembly = Assembly.GetCallingAssembly();
MessageBox.Show(assembly.FullName);
}
在 ShowAssembly
中,它将显示程序的主程序集的名称。在 ShowAssemblyAsync
中,它将显示 "mscorlib"。怎么回事?
如果您在 ShowAssembly
和 ShowAssemblyAsync
中放置断点 Visual Studio,您将获得以下调用堆栈:
DesktopClient.exe!DesktopClient.MainWindow.ShowAssembly() Line 119 C#
DesktopClient.exe!DesktopClient.MainWindow.Button_Click(object sender, System.Windows.RoutedEventArgs e) Line 109 C#
DesktopClient.exe!DesktopClient.MainWindow.ShowAssemblyAsync() Line 128 C#
DesktopClient.exe!DesktopClient.MainWindow.Button_Click(object sender, System.Windows.RoutedEventArgs e) Line 110 C#
没什么可疑的。然而,Visual Studio 是在骗你。如果我改为使用较低级别的调试器(在本例中为 WinDbg),我会得到以下调用堆栈:
00efe58c 07cf423b DesktopClient.MainWindow.ShowAssembly()
00efe5a8 07cf41dc DesktopClient.MainWindow.Button_Click(System.Object, System.Windows.RoutedEventArgs)
00efe4d8 07cf43c6 DesktopClient.MainWindow+d__10.MoveNext()
00efe51c 5ddaa48d System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Start[[System.__Canon, mscorlib]](System.__Canon ByRef)
00efe574 07cf4317 DesktopClient.MainWindow.ShowAssemblyAsync()
00efe5a8 07cf41e6 DesktopClient.MainWindow.ButtonPlus_Click(System.Object, System.Windows.RoutedEventArgs)
您可以看到来自 ShowAssemblyAsync
的调用堆栈在应用 async 关键字后已完全更改(Visual Studio 隐藏它以使调试更容易)。一个后果是该方法现在由 System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Start
调用,这就是我们在检索调用程序集时得到 "mscorlib" 的原因。
作为修复,您可以使用 Assembly.GetExecutingAssembly
(假设您的 CopyFileToDestinationAsync
方法位于正确的程序集中)或明确定位程序集:typeof(MainWindow).Assembly
。或者,就像您所做的那样,在调用方法之前检索流。
另一种方法是引入一个额外的非异步方法,以便在调用真正的异步实现之前检索程序集:
private Task CopyFileToDestinationAsync(string nameSpace,string outDirectory, string internalPath, string resourceName)
{
Assembly assembly = Assembly.GetCallingAssembly();
return CopyFileToDestinationAsync(assembly, nameSpace, outDirectory, internalPath, resourceName);
}
private async Task CopyFileToDestinationAsync(Assembly assembly string nameSpace,string outDirectory, string internalPath, string resourceName)
{
using (Stream stream = assembly.GetManifestResourceStream(nameSpace + "." + (internalPath == "" ? "" : internalPath + ".") + resourceName))
{
// ...
}
}