首页 > 解决方案 > 从 Xamarin Android 应用程序崩溃将大文件上传到 WCF

问题描述

我正在尝试从我的 xamarin 应用程序上传一个大视频(1 GB+),一旦它达到我的文件的大约 0.5 GB,它就会不断崩溃。我发现在发送数据的同时将视频发布到我的 WCF 服务的唯一方法是使用 MultiPart 逻辑,但我不确定我是否内存不足或什么原因,因为即使在调试模式下,它只是崩溃而没有任何真正的错误消息。

我正在尝试在本机设备(不是 sim)上运行它,它是带有 Android 9 的三星 Galaxy S9。

这是我正在使用的上传代码:(ps - 作为测试,我尝试将 WriteAsync 放入 for 循环中,认为可能尝试编写整个演出是问题,但结果是一样的。这就是为什么你会看到那里的 MAXFILESIZEPART 常量,它只是一个等于 10000000 的整数。)

private async Task<byte[]> GetMultipartFormDataAsync(Dictionary<string, object> postParameters, string boundary)
        {
            try
            {

                using (Stream formDataStream = new System.IO.MemoryStream())
                {
                    bool needsCLRF = false;

                    foreach (var param in postParameters)
                    {
                        // Thanks to feedback from commenters, add a CRLF to allow multiple parameters to be added.
                        // Skip it on the first parameter, add it to subsequent parameters.
                        if (needsCLRF)
                            await formDataStream.WriteAsync(Encoding.UTF8.GetBytes("\r\n"), 0, Encoding.UTF8.GetByteCount("\r\n"));

                        needsCLRF = true;

                        if (param.Value is FileParameter)
                        {
                            FileParameter fileToUpload = (FileParameter)param.Value;

                            // Add just the first part of this param, since we will write the file data directly to the Stream
                            string header = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n",
                                                boundary,
                                                param.Key,
                                                fileToUpload.FileName ?? param.Key,
                                                fileToUpload.ContentType ?? "application/octet-stream");

                            await formDataStream.WriteAsync(Encoding.UTF8.GetBytes(header), 0, Encoding.UTF8.GetByteCount(header));

                            // Write the file data directly to the Stream, rather than serializing it to a string.
                            if (fileToUpload.File.Length > MAXFILESIZEPART)
                            {
                                for (var i = 0; i < fileToUpload.File.Length; i += MAXFILESIZEPART)
                                {
                                    var len = i + MAXFILESIZEPART > fileToUpload.File.Length
                                        ? fileToUpload.File.Length - i
                                        : MAXFILESIZEPART;
                                    await formDataStream.WriteAsync(fileToUpload.File, i, len);
                                }
                            }
                            else
                            {
                                await formDataStream.WriteAsync(fileToUpload.File, 0, fileToUpload.File.Length);
                            }

                        }
                        else
                        {
                            string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}",
                                                  boundary,
                                                  param.Key,
                                                  param.Value);
                            await formDataStream.WriteAsync(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData));
                        }
                    }

                    // Add the end of the request.  Start with a newline
                    string footer = "\r\n--" + boundary + "--\r\n";
                    await formDataStream.WriteAsync(Encoding.UTF8.GetBytes(footer), 0, Encoding.UTF8.GetByteCount(footer));

                    // Dump the Stream into a byte[]
                    formDataStream.Position = 0;
                    byte[] formData = new byte[formDataStream.Length];
                    formDataStream.Read(formData, 0, formData.Length);

                    return formData;
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

它最终在下一行失败了

await formDataStream.WriteAsync(fileToUpload.File, i, len);

但只有在某个点(大约 500MB)之后,所以我假设这是一个内存问题,但事实并非如此。有没有更好的方法来完成这项任务?我这样做是为了在上传时记录进度。我正在尝试完成类似于通过 facebook 应用程序上传大型视频的操作,以便在您继续工作时它会在后台上传。它在处理较小的文件(即 - < 500 MB)时效果很好,但这是我第一次尝试一个几乎是 gig 的文件。

注意:这发生在它开始向服务器发布任何内容之前,因此它与 IIS 或 WCF 无关。此代码仅将字节写入内存流就会崩溃。

有什么建议么?

谢谢!

标签: c#androidvisual-studiowcfxamarin

解决方案


根据你的描述,服务会在某个时间点停止,并且因为你传输的文件是1G左右,很可能是sendtimeout。在指定时间内没有传输完成,导致异常。SendTimeout指定了多长时间写操作必须在超时之前完成。默认值为 1 分钟。

在此处输入图像描述

我在我的配置文件中将sendtimeout设置为15秒。如果数据耗时超过15秒,就会出现异常。您可以将其设置为更高的值以避免超时和异常。

有关 sendtimeout 的信息,请参阅以下链接:

https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.channels.binding.sendtimeout?view=dotnet-plat-ext-3.1

更新

我认为可能是内存溢出问题。大文件可能导致内存溢出,无法同时读取。

您可以参考以下链接获取解决方案

https://docs.microsoft.com/en-us/archive/blogs/johan/are-you-getting-outofmemoryexceptions-when-uploading-large-files


推荐阅读