在WCF服务中提供以下方法签名:
public string Query(string request)
{
using (Log.BeginTimedOperation("Persist request"))
{
var messageCorrelationId = Guid.NewGuid().ToString();
var payloadURI = PayloadHelper.GetFullPath(messageCorrelationId);
PayloadHelper.PersistPayloadWithPath(request, payloadURI);
Log.Information("My service request {MessageCorrelationId} {RequestPayloadPath}", messageCorrelationId, payloadURI);
}
// DoWork here, code removed for brevity
return result;
}
以及相应的扩展方法:
public static string GetFullPath(string messageCorrelationId)
{
var folderDate = DateTime.Now.ToString("yyyyMMdd");
var folderHour = DateTime.Now.ToString("HH");
var logFolder = Path.Combine(ConfigurationManager.AppSettings["NetworkFiler"], "Payloads", folderDate, folderHour);
Directory.CreateDirectory(logFolder);
var fileName = $"{messageCorrelationId}-{"MyWCFService"}-{$"{DateTime.Now:yyyy-MM-dd-HH-mm-ss-fff}-{Guid.NewGuid():N}"}.{"xml"}";
return Path.Combine(logFolder, fileName);
}
public static void PersistPayloadWithPath(string text, string path)
{
var task = WriteFileAsync(path, text);
task.GetAwaiter().GetResult();
}
private static async Task WriteFileAsync(string path, string text)
{
try
{
byte[] buffer = Encoding.Unicode.GetBytes(text);
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
{
await stream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
}
}
catch (Exception ex)
{
Serilog.Log.Error(ex, "WriteFileAsync");
}
}
例如,如果文件正受到反病毒(猜测)或文件管理器IO速度慢的询问,则此代码将被阻止。
因此,这是一个很大的争论,即从ASP.NET中的同步方法中调用异步方法。直到今天,我仍然不知道是否存在一种可靠的方法来制造起火和遗忘机制。不是我不在乎失败,而是在catch语句和静态Serilog实例中处理“应该”。
在撰写此版本的帖子时,我突然意识到,问题之一可能实际上是记录程序,并且它是围绕文件接收器的异步包装程序。将在少数情况下进行测试。
此虫虫有任何帮助。
谢谢你, 斯蒂芬
UPDATE-ASYNC
public async Task<string> QueryAsync(string request)
{
using (Log.BeginTimedOperation("PersistAsync-Request"))
{
var messageCorrelationId = Guid.NewGuid().ToString();
var payloadURI = PayloadHelper.GetFullPath(messageCorrelationId);
await PayloadHelper.PersistPayloadWithPathAsync(request, payloadURI).ConfigureAwait(false);
Log.Information("My service request {MessageCorrelationId} {RequestPayloadPath}", messageCorrelationId, payloadURI);
}
// DoWork here, code removed for brevity
return result;
}
public static string GetFullPath(string messageCorrelationId)
{
var folderDate = DateTime.Now.ToString("yyyyMMdd");
var folderHour = DateTime.Now.ToString("HH");
var logFolder = Path.Combine(ConfigurationManager.AppSettings["NetworkFiler"], "Payloads", folderDate, folderHour);
Directory.CreateDirectory(logFolder);
var fileName = $"{messageCorrelationId}-MyWCFService-{DateTime.Now:yyyy-MM-dd-HH-mm-ss-fff}-{Guid.NewGuid():N}.xml";
return Path.Combine(logFolder, fileName);
}
public static async Task PersistPayloadWithPathAsync(string text, string path)
{
await WriteFileAsync(path, text).ConfigureAwait(false);
}
private static async Task WriteFileAsync(string path, string text)
{
try
{
byte[] buffer = Encoding.Unicode.GetBytes(text);
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
{
await stream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
}
}
catch
{
// ignored
}
}
仍然随机阻止,每20-30个请求点击一次
答案 0 :(得分:2)
因此它在new FileStream()
上受阻。 Looking at the source code,构造函数调用一个名为Init()
的方法,该方法实际上最终打开了文件。因此,它正在构造函数中执行I / O,实际上不应该这样做,因为您不能await
。
设置useAsync
应该使它异步运行,如果可以。但是也许它不能从网络驱动器异步打开文件。
所以最好的选择是在Task.Run()
内部运行它,以确保它不会被阻塞。
(不幸的是)以下内容仅适用于.NET Core:
使用File.WriteAllTextAsync
会更好,这实际上会使您的生活更轻松:
await File.WriteAllTextAsync(path, text);
由于某些原因,它的文档并没有真正解释任何内容,但其工作原理与File.WriteAllText
相同(只是异步的):
创建一个新文件,将内容写入该文件,然后关闭该文件。如果目标文件已经存在,它将被覆盖。
因此,正是您在代码中所做的事情,但是通过这种方式,您可以等待整个操作(包括打开文件)。
答案 1 :(得分:1)
问题是您的WriteFileAsync
方法。它实际上是同步运行的,直到到达第一个await
为止(这就是异步/等待的工作方式)。我相信它会挂在new FileStream(...)
上。
如果您只想结束表单同步代码,那么就足够了:
public static void PersistPayloadWithPath(string text, string path)
{
Task.Run(async () => await WriteFileAsync(path, text));
}
当您没有异步替代方法时,以上代码将为您提供帮助。但是,正如加布里埃尔·露西(Gabriel Luci)在他的回答中建议的那样,您应该选择await File.WriteAllTextAsync(path, text);
,因为它可能已针对异步工作进行了优化。
在即发即弃方案中,您仍然可以将Task.Run(...);
与await File.WriteAllTextAsync(path, text);
一起使用。
请注意,任务(WriteFileAsync
)内部的异常不会传播到调用线程。在您的情况下,这将不是问题,因为整个WriteFileAsync
方法的主体位于try-catch块中,您可以在其中记录异常。
编辑
要说明线程在异步/等待方法中的行为,请尝试以下示例(尝试运行Bar
函数的所有3种方式):
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Main thread: {Thread.CurrentThread.ManagedThreadId}");
Task.Run(async () => await Bar());
// Task.Run(() => Bar());
// Bar();
Console.ReadLine();
}
static async Task Bar()
{
Console.WriteLine($"Bar thread before await: {Thread.CurrentThread.ManagedThreadId}");
await Foo();
Console.WriteLine($"Bar thread after await: {Thread.CurrentThread.ManagedThreadId}");
}
static async Task Foo()
{
Console.WriteLine($"Foo thread before await: {Thread.CurrentThread.ManagedThreadId}");
var c = new WebClient();
var source = await c.DownloadStringTaskAsync("http://google.com");
Console.WriteLine($"Foo thread after await: {Thread.CurrentThread.ManagedThreadId}");
}
}
}
答案 2 :(得分:1)
我认为这是等待中,因为您使用
task.GetAwaiter().GetResult();
如果不需要结果,只需删除此行。它应该工作正常。
如果需要结果,则应使PersistPayloadWithPath
函数也异步。
答案 3 :(得分:0)
确定导致的根是配置错误的StealthAudit,这是我们用来审核文件管理器的工具。调整配置后,一切都会按预期进行。