【问题标题】:Serialization taking way too long序列化时间太长
【发布时间】:2017-07-23 13:05:30
【问题描述】:

所以我正在开发一个在可移植类库 (Xamarin) 中创建的 UWP 应用程序。我需要将用户输入的信息(例如在 TextBoxes 中)保存在 xml 文件中。

因此,我在 PCL 中创建了一个类,从这些 TextBox 中获取信息:

namespace myProject
{
    public class XMLData
    {
        [XmlRoot("MyRootElement")]
        public class MyRootElement
        {
            [XmlAttribute("MyAttribute1")] //name of the xml element
            public string MyAttribute1     //name of a textboxt e.g.
            {
                get;
                set;
            }
            [XmlAttribute("MyAttribute2")]
            public string MyAttribute2
            {
                get;
                set;
            }
            [XmlElement("MyElement1")]
            public string MyElement1
            {
                get;
                set;
            }
    }
}

每个页面上都有一个“继续”按钮。点击后,数据被保存:

async void Continue_Clicked(object sender, EventArgs e)
        {
            await Navigation.PushAsync(new Page2());
            XMLData.MyRootElement mre = new XMLData.MyRootElement
            {
                MyAttribute1 = editor1.ToString(),
                MyAttribute2 = editor2.ToString(),
                MyElement1 = editor3.ToString()
            };
        }

点击最后​​一个按钮,文件应该被创建并保存:

private void CreateandSave_Clicked(object sender, EventArgs e)
        {
            var s = DependencyService.Get<IFileHelper>().MakeFileStream();//using dependencyService to get a stream (there is no system.io.stream in PCL)
            XMLData xmldat = new XMLData();
            using (StreamWriter sw = new StreamWriter(s, Encoding.UTF8))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(XMLData));
                serializer.Serialize(sw, xmldat);
            }
        }

这是我在 UWP 类中的代码(对于dependencyService,我创建了一个名为 FileHelper 的类来获取流并创建保存位置 + 文件)

namespace myProject.UWP
{
    public class FileHelper: IFileHelper //IFileHelper is a simple interface I made with the Stream MakeFileStream(); method in it
    {
        public async Task<IRandomAccessStream> MakeFileStreamAsync()
        {
            StorageFolder sf = KnownFolders.DocumentsLibrary;

            var file = await sf.CreateFileAsync("data.xml", CreationCollisionOption.OpenIfExists);
            using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
            {
                return stream;
            }
        }
        Stream IFileHelper.MakeFileStream()
        {
            var task = MakeFileStreamAsync();
            task.Wait();
            return task.Result.AsStreamForWrite();
        }
    }
}

问题是每当我到达 CreateandSave 按钮并单击它时,应用程序就会冻结。没有错误,没有,一切看起来都很好。在我分解调试后,我可以看到在我想要的文件夹中创建了一个 xml 文件,但它是空的(0 字节)。代码有什么问题?有人有想法吗?

【问题讨论】:

  • 为什么不在接口中添加一个方法,例如“WriteStringToFileAsync(string s)”,然后在每个平台上实现它。所以你不必在两个类之间传递流。这应该会使调试变得容易得多。
  • @Malte 我对编程很陌生,我认为您的建议会超出我的理解。我不太明白你的意思或我将如何实现它。不过还是谢谢你的建议,我很感激!
  • 您可以将方法“WriteStringToFileAsync(string s)”添加到您的接口 IFileHelper 而不是 MakeFileStream,在每个平台上实现此方法(例如带有 Windows.Storage.FileIO.WriteTextAsync 的 UWP)然后序列化您的数据到 XML 字符串从 DependencyService 获取 IFileHelper 的实现,并将字符串传递给 WriteStringToFile 方法。我希望这可以帮助您理解我要解释的内容。
  • @Malte 知道了,谢谢!但是冻结的问题仍然存在:(
  • 冻结是从哪一行代码开始的?

标签: c# xml serialization xamarin uwp


【解决方案1】:

在这段代码中:

using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
    return stream;
}

您正在返回由using 块创建的实例。它将在返回之前被释放,因此,您返回的是一个已释放的对象。

将其更改为 return stream。您使用的StreamWriter 本身位于using 块中,因此在处理it will dispose the underlying stream 期间:

StreamWriter.Dispose 被调用时,StreamWriter 对象在提供的Stream 对象上调用Dispose()

【讨论】:

  • 好的,所以我必须把它写在using 之外,对吧? @BartoszKP
  • @RoloffM 是的,最简单的解决方法是使用块删除它。
  • 谢谢!我现在已经更改了代码,但问题仍然存在。没有任何错误,但应用程序只是冻结...
  • @RoloffM 似乎您的应用程序的异步结构中还有一些问题:/ 我会调查一下,但我在这方面不是很有经验。
  • 谢谢您,先生,我很感激!问题是我也不是该领域的专家:(
【解决方案2】:

您的冻结问题是 Task.Wait() 指令阻塞主 UI 线程,直到 MakeFileStreamAsync() 方法完成执行。

您应该将此方法设为async 并返回Task&lt;Stream&gt; 类型,并使用await 关键字调用MakeFileStreamAsync 方法:

async Task<Stream> IFileHelper.MakeFileStream()
{
    var stream = await MakeFileStreamAsync();
    return stream.AsStreamForWrite();
}

因此,您的创建点击代码应类似于:

private void CreateandSave_Clicked(object sender, EventArgs e)
{
    var s = DependencyService.Get<IFileHelper>().MakeFileStream();//using dependencyService to get a stream (there is no system.io.stream in PCL)
    XMLData xmldat = new XMLData();

    // Here you should await your `s` Task:
    await s;

    using (StreamWriter sw = new StreamWriter(s, Encoding.UTF8))
    {
        XmlSerializer serializer = new XmlSerializer(typeof(XMLData));
        serializer.Serialize(sw, xmldat);
    }
}

希望对你有帮助!

编辑: 关于您的空xml文件问题,我认为这是因为您将数据保存在另一个页面中,但不对其进行任何处理。这意味着您在加载Page2 时会丢失它们。因此,它们在 CreateandSave_Clicked 方法中不可用,您当前保存的是一个空的 XMLData 对象。

更直观的方法是将数据传递给Page2 构造函数,并将此数据类型添加为Page2 的公共属性。所以你的Continue_Clicked 方法看起来像:

async void Continue_Clicked(object sender, EventArgs e)
{
    // Note you must REVERSE instructions here
    // Create first your object (save the user data in it)
    XMLData.MyRootElement mre = new XMLData.MyRootElement
    {
        MyAttribute1 = editor1.ToString(),
        MyAttribute2 = editor2.ToString(),
        MyElement1 = editor3.ToString()
    };
    // Pass it to Page2 through the constructor
    await Navigation.PushAsync(new Page2(mre));
}

所以Page2 类/构造函数变为:

public class Page2 : SomeParentClass
{
   ...
   // add your XMLData property
   public XMLData.MyRootElement mre { get; set; }
   ...
   // the constructor
   public Page2(XMLData.MyRootElement data){
       // Save the user data in xmldat property. So this data could be reused later.
       this.mre = data;
   }
}

那么,如果是 Page2 自己负责创建和保存 XML 文件,则可以重用通过构造函数传递的对象。

private void CreateandSave_Clicked(object sender, EventArgs e)
{
    var s = DependencyService.Get<IFileHelper>().MakeFileStream();//using dependencyService to get a stream (there is no system.io.stream in PCL)
    // You want to remove that here as you created a public property of type XMLData.MyRootElement (called mre) holding user data instead 
    //XMLData xmldat = new XMLData();

    // Here you should await your `s` Task:
    await s;

    using (StreamWriter sw = new StreamWriter(s, Encoding.UTF8))
    {
        // Change of serialized type here
        XmlSerializer serializer = new XmlSerializer(typeof(XMLData.MyRootElement));
        // Here, just seralize the property saved through constructor
        serializer.Serialize(sw, mre);
    }
}

请注意,如果是 Page3 或任何 Page 负责保存 XML 文件,只需将保存的用户数据逐页传递,并在调用 CreateandSave_Clicked 方法时保存一次。

另一点是我不确定将MyRootElement 类嵌套到XMLData 中是否有用。您可以删除 XMLData 嵌套类,并将 MyRootElement 类保留为“主”类。

您也可以使用静态字段来实现相同的目的,因此不使用构造函数。但在我看来,它不那么直观,而且也不那么干净。

享受,

【讨论】:

  • 谢谢@TaiT 的!你能向我解释一下await s; 的工作原理吗?我之前需要添加什么?因为出现“类型或命名空间名称'await'...”错误。
  • 好的,我现在知道了,没有冻结!有用!太感谢了!现在唯一的问题是 xml 文件是空的 :( 模板在那里,但没有我的元素,知道吗?
  • 很高兴它有帮助!有关更多信息,我发现 this microsoft page 在学习此主题时非常有帮助。 Async/Await 真的很容易理解但很难掌握 ;)。请特别查看异步方法中发生的情况部分中的架构。关于你的空文件,我可能稍后再看,现在我没时间了:)
  • 我会的,非常感谢!好的,但请不要忘记我,如果您有时间请稍后再看:)祝您有美好的一天!
  • 好吧 5 小时后我发现它不允许有多个 rootElement(类),否则我无法传递参数。我明天再试一次,看看我能做什么.非常感谢!
猜你喜欢
  • 1970-01-01
  • 2015-12-01
  • 1970-01-01
  • 2017-10-06
  • 2016-12-03
  • 2012-07-20
  • 2011-07-03
  • 2015-07-05
  • 1970-01-01
相关资源
最近更新 更多