【问题标题】:Why is GetManifestResourceStream returning null if used inside an async Method?如果在异步方法中使用,为什么 GetManifestResourceStream 返回 null?
【发布时间】:2020-03-28 12:26:49
【问题描述】:

在我的程序中,我正在将资源文件写入用户选择的某个位置。为此,我正在使用 GetManifestResourceStream。一切正常。

接下来我想让我的书写操作不阻塞 UI。所以我使用异步等待更改了代码。 不幸的是 GetManifestRresourceStream 现在返回 null 。在不使用 async await 的情况下改回来一切正常。

我做错了什么以及如何解决它,以便我能够复制文件并且 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();
        }

【问题讨论】:

  • 我通过在外部调用 GetManifestResourceStream 函数并将 Stream 作为参数传递给异步函数来解决了这个问题。但是我真的很想知道为什么它不像我以前尝试过的那样运行。
  • Assembly.GetCallingAssembly 很可能没有返回您期望的程序集
  • 是的,也许。我也尝试使用其他的。但这也无济于事。

标签: c# wpf async-await .net-assembly


【解决方案1】:

由于异步的工作原理,这是一个有趣的边缘案例。

让我们采用那些测试方法:

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”。怎么回事?

如果您在 Visual Studio 中的 ShowAssemblyShowAssemblyAsync 中放置断点,您将获得以下调用堆栈:

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))
    {
        // ...
    }
}

【讨论】:

  • 非常感谢。这对更好地理解很有帮助。
猜你喜欢
  • 1970-01-01
  • 2014-03-05
  • 1970-01-01
  • 2014-04-25
  • 2013-05-03
  • 2018-01-24
  • 1970-01-01
  • 2013-01-02
  • 1970-01-01
相关资源
最近更新 更多