c# - 处理 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();
}
解决方案
首先,摆脱 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;
}
推荐阅读
- git - 我可以在现有提交下进行 git 提交而不在下一次提交中保留其任何内容吗?(只是为了让代码成为历史)
- sql - 如何在同一行中返回每周轮班开始和结束预定时间?
- python - 如何让 python 识别字符串中的正确子字符串?
- python - PyQt5:如何使用计时器每秒增加一个变量?
- html - 基于输入长度的动态宽度(和中心)输入
- javascript - 用于唤醒应用程序以进行 webrtc 调用的 Cordova 插件
- python - 使用 BeautifulSoup 遍历 XML 并拉取下一个兄弟
- django - 我必须将数据导入扩展到由 Django ORM 定义表结构的数据库中的常见方法是什么?
- ubuntu - 在 ubuntu 上永久编辑 PATH
- javascript - 如何使用 JavaScript 更改伪类元素?