在 DataPackage.SetData 或 DataPackage.GetDataAsync 之后处理 IRandomAccessStream?

Dispose IRandomAccessStream after DataPackage.SetData or DataPackage.GetDataAsync?

考虑使用 SetData 将数据放入 windows 剪贴板 DataPackage,然后使用 GetDataAsync 检索它,如下所示:

        IEnumerable<T> objects = ...; 
        var randomAccessStream = new InMemoryRandomAccessStream();
        using (XmlDictionaryWriter xmlWriter = XmlDictionaryWriter.CreateTextWriter(randomAccessStream.AsStreamForWrite(), Encoding.Unicode)) {
            var serializer = new DataContractSerializer(typeof(T), knownTypes);
            foreach (T obj in objects) {
                serializer.WriteObject(xmlWriter, obj);
            }
        }
        dataPackage.SetData(formatId, randomAccessStream);

然后稍后(例如 Clipboard.ContentsChanged),

        randomAccessStream = await dataPackageView.GetDataAsync(formatId) as IRandomAccessStream;
        xmlReader = XmlDictionaryReader.CreateTextReader(randomAccessStream.AsStreamForRead(), Encoding.Unicode, XmlDictionaryReaderQuotas.Max, (OnXmlDictionaryReaderClose?)null);
        var serializer = new DataContractSerializer(typeof(T), knownTypes);
        while (serializer.IsStartObject(xmlReader)) {
            object? obj = serializer.ReadObject(xmlReader);
            ...
        }
        xmlReader.Dispose(); // in the real code, this is in a finally clause

我的问题是,我什么时候处理 randomAccessStream?我进行了一些搜索,我看到的所有使用 SetData 和 GetDataAsync 的示例都完全没有处理放入数据包或从数据包中获取的对象。

我应该在 SetData 之后,在 GetDataAsync 之后,在 DataPackage.OperationCompleted 中,在它们的某种组合中,还是在它们的 none 中处理它?

sjb

P.S。如果我可以在这里提出第二个问题...当我使用 dataPackage.Properties.Add("IEnumerable", entities) 将引用放入 DataPackage 时,它​​是否会产生安全风险 - 其他应用程序是否可以访问参考 并使用它?

when do I dispose the randomAccessStream?

Only Dispose stream 当你用完它时,当你 Diposed stream 它将不再可用在任何其他上下文,即使您在其他对象实例中存储或传递了对它的多个引用。

如果您是在谈论 SetData() 逻辑中引用的原始流,那么从另一个角度来看,如果您过早处置,使用代码将无法再访问该流,并将失败。

作为一般规则,我们应该尝试设计逻辑,以便在任何给定的时间点对于任何给定的流都有一个明确且单一的 Owner,这样它应该清楚谁负责 Disposing 流。这种对稍微不同的场景的响应很好地解释了这一点, 然而,作为一般模式,只有创建流的范围应该负责 处理 它。

  • 唯一的例外是,如果您需要在创建方法之外访问流,那么父 class 应该持有对它的引用,在这种情况下您应该使父 class 实施 IDisposable 并确保它清除所有可能闲置的资源。

您在文档中看不到这一点的原因通常是围绕调用 Dispose() 的时间的细微差别超出了范围,或者会在为其他目的而设计的示例中丢失。

  • 特别针对流通过 any 机制传递并稍后使用的示例,与 DataPackage 一样,很难显示所有编排代码以涵盖使用 DataPackage.SetData(...) 存储流和稍后通过 DataPackage.GetDataAsync(...)
  • 访问流之间的时间
  • 还要考虑 DataPackage 最常见的场景,其中消费者不仅在不同的逻辑范围内,而且很可能在完全不同的应用程序域中,包括所有代码以涵盖何时或是否调用 dispose 应该包含 2 个不同应用程序的整个代码库。

tldr

剪贴板旨在 应用程序之间传递内容,并且只能传递字符串内容或对文件的引用,所有其他内容必须序列化为字符串,或保存到文件,或者必须像文件一样运行,才能通过剪贴板跨应用程序域访问。

对于通过剪贴板传递自定义数据和格式有支持和指导,最终这涉及围绕“如何在提供者端准备内容”和“如何在消费者端解释内容”的离散管理”。如果你可以为此使用简单的序列化,那么 KISS.

IEnumerable<Test> objectsIn = new Test[] { new Test { Name = "One" }, new Test { Name = "two" } };
var dataPackage = new DataPackage();
dataPackage.SetData("MyCustomFormat", Newtonsoft.Json.JsonConvert.SerializeObject(objectsIn));
Clipboard.SetContent(dataPackage);

...

var dataPackageView = Clipboard.GetContent();
string contentJson = (await dataPackageView.GetDataAsync("MyCustomFormat")) as string;
IEnumerable<Test> objectsOut = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<Test>>(contentJson);

在 WinRT 中,DataPackageView class 实现确实支持传递 但是,就生命周期而言,正常规则适用于流,如果流是处置与否。这对于传输大量内容或当消费者可能请求不同格式的内容时很有用。

  • 如果您对此没有高级需求,或者您不传输基于文件或图像的资源,则不需要使用流来传输数据。

DataPackageView - Remarks
During a share operation, the source app puts the data being shared in a DataPackage object and sends that object to the target app for processing. The DataPackage class includes a number of methods to support the following default formats: text, Rtf, Html, Bitmap, and StorageItems. It also has methods to support custom data formats. To use these formats, both the source app and target app must already be aware that the custom format exists.

  • OPs attempt to save a stream to the Clipboard 在这种情况下是将任意或 custom 对象保存到剪贴板的示例,它既不是字符串也不是指针一个文件,因此 OS 级别没有处理此信息的本机方法。

从历史上看,将字符串数据或文件引用放到剪贴板上会有效地将此信息广播到同一 运行 OS 上的 所有 个应用程序,然而 Windows 10 通过使您的剪贴板内容也能够跨设备同步来扩展这一点。 DataTransfer 命名空间实现允许您影响此可用性的范围,但最终此功能旨在允许您将数据推送到当前应用程序沙盒域之外。

因此,无论您选择自己序列化内容,还是希望 DataTransfer 实现尝试为您完成,如果内容还不是字符串或文件引用格式,都会被序列化,并且序列化的内容,如果成功,将提供给消费者。

通过这种方式,不会出现内存泄漏或安全问题,您可能会无意中向外部进程提供对当前进程内存或执行上下文的访问权限,但 data 安全性仍然是一个问题,所以不要使用剪贴板传递敏感内容。


ArbitraryCustom data

的更简单示例

OPs 示例是将 IEnumerable<T> 对象集合放到剪贴板上,稍后再检索它们。 OP 选择通过 DataContractSerializer 使用 XML 序列化 但是保存了对序列化程序使用的流的引用到剪贴板,而不是实际内容。

有很多管道和第一原则逻辑在进行,但收效甚微,如果您要 流式传输 内容,流很有用,所以如果您要允许消费者 控制流 但是如果你打算在一个同步进程中写入流,那么最好完全关闭流并绕过 缓冲区,你通过你的流填充,我们甚至不会尝试在以后的时间点重新使用相同的流。

以下解决方案适用于 WinRT 中的 Clipboard 访问以预序列化对象集合并将它们传递给使用者:

IEnumerable<Test> objectsIn = new Test[] { new Test { Name = "One" }, new Test { Name = "two" } };

var dataPackage = new DataPackage();
string formatId = "MyCustomFormat";

var serial = Newtonsoft.Json.JsonConvert.SerializeObject(objectsIn);
dataPackage.SetData(formatId, serial);
Clipboard.SetContent(dataPackage);

然后在一个可能完全不同的应用程序中:

string formatId = "MyCustomFormat";
var dataPackageView = Clipboard.GetContent();
object content = await dataPackageView.GetDataAsync(formatId);
string contentString = content as string;
var objectsOut = Newtonsoft.Json.JsonConvert.DeserializeObject<IEnumerable<Test>>(contentString);

foreach (var o in objectsOut)
{
    Console.WriteLine(o);
}

提供者和消费者应用程序上下文中的测试定义:

public class Test
{
    public string Name { get; set; }
}