如果在异步方法中使用,为什么 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"。怎么回事?

如果您在 ShowAssemblyShowAssemblyAsync 中放置断点 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))
    {
        // ...
    }
}