首页 > 解决方案 > 处理 API 的并行 POST

问题描述

我有一个 API Post 方法,它采用一个字符串,该字符串表示 API 转换为 PDF 的 word 文档中的 Bae64 字节字符串。我的测试客户端向要转换的 API 发送多个文档,每个文档都有自己的任务。问题在于并发和写入文件。由于调用是并行的,因此我最终得到了一个正在使用的文件。我尝试了很多不同的方法来阻止转换过程,直到文档被转换但没有一个起作用。如果只是转换单个文件,一切正常,但只要它是 2 或更多,问题就会发生。谁能指导我正确的方向来解决这个问题?

接口:

        [HttpPost]
        public async Task<SimpleResponse> Post([FromBody]string request)
        {
            var response = new SimpleResponse();
            Task t = Task.Factory.StartNew(async () =>
            {
                try
                {
                    Converter convert = new Converter();

                    var result = await convert.CovertDocToPDF(request, WebConfigurationManager.AppSettings["tempDocPath"], WebConfigurationManager.AppSettings["tempPdfPath"]);

                    response.Result = result;
                    response.Success = true;

                }
                catch (Exception ex)
                {
                    response.Exception = ex;
                    response.Success = false;
                    response.Errors = new List<string>();
                    response.Errors.Add(string.Format("{0}, {1}", ex.Message, ex.InnerException?.Message ?? ""));
                }
            });
            t.Wait();
            return response;
        }

转换代码

        public Task<string> CovertDocToPDF(string blob, string tempDocPath, string tempPdfPath)
        {
            try
            {
                // Convert blob back to bytes
                byte[] bte = Convert.FromBase64String(blob);

                // Process and return blob
                return Process(bte, tempDocPath, tempPdfPath);
            }
            catch (Exception Ex)
            {
                throw Ex;
            }            
        }

        private async Task<string> Process(byte[] bytes, string tempDocPath, string tempPdfPath)
        {
            try
            {
                string rs = RandomString(16, true);
                tempDocPath = tempDocPath + rs + ".docx";
                tempPdfPath = tempPdfPath + rs + ".pdf";

                // This is where the problem happens with concurrent calls. I added
                // the try catch when the file is in use to generate a new
                // filename but the error still happens.
                try
                {
                    // Create a temp file
                    File.WriteAllBytes(tempDocPath, bytes);
                }
                catch (Exception Ex)
                {
                    rs = RandomString(16, true);
                    tempDocPath = tempDocPath + rs + ".docx";
                    tempPdfPath = tempPdfPath + rs + ".pdf";
                    File.WriteAllBytes(tempDocPath, bytes);
                }

                word.Application app = new word.Application();
                word.Document doc = app.Documents.Open(tempDocPath);
                doc.SaveAs2(tempPdfPath, word.WdSaveFormat.wdFormatPDF);

                doc.Close();
                app.Quit(); // Clean up the word instance.

                // Need the bytes to return the blob
                byte[] pdfFileBytes = File.ReadAllBytes(tempPdfPath);

                // Delete temp files
                File.Delete(tempDocPath);
                File.Delete(tempPdfPath);

                // return blob
                return Convert.ToBase64String(pdfFileBytes);
            }
            catch (Exception Ex)
            {
                throw Ex;
            }
        }

客户:

        public async void btnConvert_Click(object sender, EventArgs e)
        {
            var response = await StartConvert();            

            foreach (SimpleResponse sr in response)
            {
                if (sr.Success)
                {
                    byte[] bte = Convert.FromBase64String(sr.Result.ToString());

                    string rs = RandomString(16, true);
                    string pdfFileName = tempPdfPath + rs + ".pdf";

                    if (File.Exists(pdfFileName))
                    {
                        File.Delete(pdfFileName);
                    }

                    System.IO.File.WriteAllBytes(pdfFileName, bte);
                }
                else
                {
                }
            }
        }

        private async Task<IEnumerable<SimpleResponse>> StartConvert()
        {
            var tasks = new List<Task<SimpleResponse>>();

            foreach (string s in docPaths)
            {
                byte[] bte = File.ReadAllBytes(s);

                tasks.Add(ConvertDocuments(Convert.ToBase64String(bte)));
            }

            return (await Task.WhenAll(tasks));
        }


        private async Task<SimpleResponse> ConvertDocuments(string requests)
        {
            using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
            {              
                client.BaseAddress = new Uri(BaseApiUrl);
                client.DefaultRequestHeaders.Add("Accept", "application/json");

                // Add an Accept header for JSON format.    
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//application/json
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, BaseApiUrl + ApiUrl);

                var data = JsonConvert.SerializeObject(requests);

                request.Content = new StringContent(data, Encoding.UTF8, "application/json");

                HttpResponseMessage response1 = await client.PostAsync(BaseApiUrl + ApiUrl, request.Content).ConfigureAwait(false);
                var response = JsonConvert.DeserializeObject<SimpleResponse>(await response1.Content.ReadAsStringAsync());

               
                return response;
            }            
        }

随机字符串生成器

        public string RandomString(int size, bool lowerCase = false)
        {
            var builder = new StringBuilder(size);

            // Unicode/ASCII Letters are divided into two blocks
            // (Letters 65–90 / 97–122):   
            // The first group containing the uppercase letters and
            // the second group containing the lowercase.  

            // char is a single Unicode character  
            char offset = lowerCase ? 'a' : 'A';
            const int lettersOffset = 26; // A...Z or a..z: length = 26  

            for (var i = 0; i < size; i++)
            {
                var @char = (char)_random.Next(offset, offset + lettersOffset);
                builder.Append(@char);
            }

            return lowerCase ? builder.ToString().ToLower() : builder.ToString();
        }

标签: c#restparallel-processingtask

解决方案


首先,摆脱 Task.Factory.StartNew ... t.Wait() - 你不需要额外的任务,根级别的方法是异步的,你的阻塞等待只是通过同步阻塞破坏了异步的好处。

其次,就像上面建议的评论一样,文件名随机字符串生成器很可能不是真正随机的。要么不为你的伪随机生成的种子值提供任何东西,要么使用类似 Environment.TickCount 的东西,这应该足够了。Guid.NewGuid() 也可以。

临时文件的另一个不错的选择是 Path.GetTempFileName(也会为您生成一个空文件):https ://docs.microsoft.com/en-us/dotnet/api/system.io.path.gettempfilename?view=netstandard- 2.0

[HttpPost]
public async Task<SimpleResponse> Post([FromBody]string request)
{
    var response = new SimpleResponse();
    try
    {
        ...

        var result = await convert.CovertDocToPDF(...);

        ...
    }
    catch (Exception ex)
    {
        ...
    }
    
    return response;
}

推荐阅读