C# 使用Task.Run而不是Delegate.BeginInvoke
我最近将我的项目升级到ASP.NET 4.5,我已经等了很长时间才使用4.5的异步功能。阅读文档后,我不确定是否可以改进代码 我希望异步执行任务,然后忘记它。我目前的做法是创建代理,然后使用C# 使用Task.Run而不是Delegate.BeginInvoke,c#,asynchronous,C#,Asynchronous,我最近将我的项目升级到ASP.NET 4.5,我已经等了很长时间才使用4.5的异步功能。阅读文档后,我不确定是否可以改进代码 我希望异步执行任务,然后忘记它。我目前的做法是创建代理,然后使用BeginInvoke 下面是我的项目中的一个过滤器,每当用户访问必须审核的资源时,它都会在我们的数据库中创建一个审核: public override void OnActionExecuting(ActionExecutingContext filterContext) { var request
BeginInvoke
下面是我的项目中的一个过滤器,每当用户访问必须审核的资源时,它都会在我们的数据库中创建一个审核:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;
var id = WebSecurity.CurrentUserId;
var invoker = new MethodInvoker(delegate
{
var audit = new Audit
{
Id = Guid.NewGuid(),
IPAddress = request.UserHostAddress,
UserId = id,
Resource = request.RawUrl,
Timestamp = DateTime.UtcNow
};
var database = (new NinjectBinder()).Kernel.Get<IDatabaseWorker>();
database.Audits.InsertOrUpdate(audit);
database.Save();
});
invoker.BeginInvoke(StopAsynchronousMethod, invoker);
base.OnActionExecuting(filterContext);
}
我宁愿不使用回调,因为我不需要异步调用的任务的结果
如何使用
Task.Run()
(或async
和wait
)改进此代码?如果我正确理解了您的需求,您希望启动一项任务,然后忘记它。当任务完成时,如果发生异常,则要将其记录下来
我会使用Task.Run
创建一个任务,然后使用ContinueWith
附加一个继续任务。此继续任务将记录从父任务引发的任何异常。另外,使用TaskContinuationOptions.OnlyOnFaulted
确保仅在发生异常时才运行继续
Task.Run(() => {
var audit = new Audit
{
Id = Guid.NewGuid(),
IPAddress = request.UserHostAddress,
UserId = id,
Resource = request.RawUrl,
Timestamp = DateTime.UtcNow
};
var database = (new NinjectBinder()).Kernel.Get<IDatabaseWorker>();
database.Audits.InsertOrUpdate(audit);
database.Save();
}).ContinueWith(task => {
task.Exception.Handle(ex => {
var username = WebSecurity.CurrentUserName;
Debugging.DispatchExceptionEmail(ex, username);
});
}, TaskContinuationOptions.OnlyOnFaulted);
Task.Run(()=>{
var审计=新审计
{
Id=Guid.NewGuid(),
IPAddress=request.UserHostAddress,
UserId=id,
Resource=request.RawUrl,
Timestamp=DateTime.UtcNow
};
var数据库=(新的NinjectBinder()).Kernel.Get();
数据库.Audits.InsertOrUpdate(审计);
Save();
}).ContinueWith(任务=>{
task.Exception.Handle(ex=>{
var username=WebSecurity.CurrentUserName;
调试.DispatchExceptionEmail(例如,用户名);
});
},TaskContinuationOptions.OnlyOnFaulted);
作为补充说明,ASP.NET中的后台任务和触发并忘记场景是非常不鼓励的。看
下面是我的项目中的一个过滤器,每当用户访问必须审核的资源时,它都会在我们的数据库中创建一个审核
审计当然不是我所说的“一劳永逸”。请记住,在ASP.NET上。因此,如果您想要的语义是审计可能偶尔会丢失,那么(并且只有在那时)您可以使用fire和forget进行审计
如果要确保您的审核都是正确的,请等待审核保存完成,然后再发送响应,或者将审核信息排队到可靠的存储(例如Azure队列或MSMQ),并让独立的后端(例如Azure worker角色或Win32服务)处理该队列中的审核
但是,如果您想过一种危险的生活(承认偶尔会丢失审计),您可以通过向ASP.NET运行时注册工作来缓解问题。使用:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request=filterContext.HttpContext.request;
var id=WebSecurity.CurrentUserId;
BackgroundTaskManager.Run(()=>
{
尝试
{
var审计=新审计
{
Id=Guid.NewGuid(),
IPAddress=request.UserHostAddress,
UserId=id,
Resource=request.RawUrl,
Timestamp=DateTime.UtcNow
};
var数据库=(新的NinjectBinder()).Kernel.Get();
数据库.Audits.InsertOrUpdate(审计);
Save();
}
捕获(例外e)
{
var username=WebSecurity.CurrentUserName;
调试。DispatchExceptionEmail(电子邮件,用户名);
}
});
base.OnActionExecuting(filterContext);
}
这听起来可能有点超出范围,但如果您只是想在启动它之后忘记它,为什么不直接使用线程池呢
比如:
ThreadPool.QueueUserWorkItem(
x =>
{
try
{
// Do something
...
}
catch (Exception e)
{
// Log something
...
}
});
我不得不对不同的异步调用方法进行一些性能基准测试,我发现(毫不奇怪)ThreadPool
工作得更好,但实际上,BeginInvoke
并没有那么糟糕(我在.NET 4.5上)。这就是我在文章末尾发现的代码。我没有在网上找到类似的东西,所以我自己花时间检查了一下。每个调用并不完全相等,但就其功能而言,在功能上或多或少是相等的:
线程池
:70.80ms
任务
:90.88毫秒
BeginInvoke
:121.88ms
螺纹
:4657.52ms
public class Program
{
public delegate void ThisDoesSomething();
// Perform a very simple operation to see the overhead of
// different async calls types.
public static void Main(string[] args)
{
const int repetitions = 25;
const int calls = 1000;
var results = new List<Tuple<string, double>>();
Console.WriteLine(
"{0} parallel calls, {1} repetitions for better statistics\n",
calls,
repetitions);
// Threads
Console.Write("Running Threads");
results.Add(new Tuple<string, double>("Threads", RunOnThreads(repetitions, calls)));
Console.WriteLine();
// BeginInvoke
Console.Write("Running BeginInvoke");
results.Add(new Tuple<string, double>("BeginInvoke", RunOnBeginInvoke(repetitions, calls)));
Console.WriteLine();
// Tasks
Console.Write("Running Tasks");
results.Add(new Tuple<string, double>("Tasks", RunOnTasks(repetitions, calls)));
Console.WriteLine();
// Thread Pool
Console.Write("Running Thread pool");
results.Add(new Tuple<string, double>("ThreadPool", RunOnThreadPool(repetitions, calls)));
Console.WriteLine();
Console.WriteLine();
// Show results
results = results.OrderBy(rs => rs.Item2).ToList();
foreach (var result in results)
{
Console.WriteLine(
"{0}: Done in {1}ms avg",
result.Item1,
(result.Item2 / repetitions).ToString("0.00"));
}
Console.WriteLine("Press a key to exit");
Console.ReadKey();
}
/// <summary>
/// The do stuff.
/// </summary>
public static void DoStuff()
{
Console.Write("*");
}
public static double RunOnThreads(int repetitions, int calls)
{
var totalMs = 0.0;
for (var j = 0; j < repetitions; j++)
{
Console.Write(".");
var toProcess = calls;
var stopwatch = new Stopwatch();
var resetEvent = new ManualResetEvent(false);
var threadList = new List<Thread>();
for (var i = 0; i < calls; i++)
{
threadList.Add(new Thread(() =>
{
// Do something
DoStuff();
// Safely decrement the counter
if (Interlocked.Decrement(ref toProcess) == 0)
{
resetEvent.Set();
}
}));
}
stopwatch.Start();
foreach (var thread in threadList)
{
thread.Start();
}
resetEvent.WaitOne();
stopwatch.Stop();
totalMs += stopwatch.ElapsedMilliseconds;
}
return totalMs;
}
public static double RunOnThreadPool(int repetitions, int calls)
{
var totalMs = 0.0;
for (var j = 0; j < repetitions; j++)
{
Console.Write(".");
var toProcess = calls;
var resetEvent = new ManualResetEvent(false);
var stopwatch = new Stopwatch();
var list = new List<int>();
for (var i = 0; i < calls; i++)
{
list.Add(i);
}
stopwatch.Start();
for (var i = 0; i < calls; i++)
{
ThreadPool.QueueUserWorkItem(
x =>
{
// Do something
DoStuff();
// Safely decrement the counter
if (Interlocked.Decrement(ref toProcess) == 0)
{
resetEvent.Set();
}
},
list[i]);
}
resetEvent.WaitOne();
stopwatch.Stop();
totalMs += stopwatch.ElapsedMilliseconds;
}
return totalMs;
}
public static double RunOnBeginInvoke(int repetitions, int calls)
{
var totalMs = 0.0;
for (var j = 0; j < repetitions; j++)
{
Console.Write(".");
var beginInvokeStopwatch = new Stopwatch();
var delegateList = new List<ThisDoesSomething>();
var resultsList = new List<IAsyncResult>();
for (var i = 0; i < calls; i++)
{
delegateList.Add(DoStuff);
}
beginInvokeStopwatch.Start();
foreach (var delegateToCall in delegateList)
{
resultsList.Add(delegateToCall.BeginInvoke(null, null));
}
// We lose a bit of accuracy, but if the loop is big enough,
// it should not really matter
while (resultsList.Any(rs => !rs.IsCompleted))
{
Thread.Sleep(10);
}
beginInvokeStopwatch.Stop();
totalMs += beginInvokeStopwatch.ElapsedMilliseconds;
}
return totalMs;
}
public static double RunOnTasks(int repetitions, int calls)
{
var totalMs = 0.0;
for (var j = 0; j < repetitions; j++)
{
Console.Write(".");
var resultsList = new List<Task>();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < calls; i++)
{
resultsList.Add(Task.Factory.StartNew(DoStuff));
}
// We lose a bit of accuracy, but if the loop is big enough,
// it should not really matter
while (resultsList.Any(task => !task.IsCompleted))
{
Thread.Sleep(10);
}
stopwatch.Stop();
totalMs += stopwatch.ElapsedMilliseconds;
}
return totalMs;
}
}
公共类程序
{
公共委托无效ThisDoesMething();
//执行一个非常简单的操作以查看
//不同的异步调用类型。
公共静态void Main(字符串[]args)
{
常量整数重复=25;
const int calls=1000;
var results=新列表();
控制台写入线(
“{0}个并行调用,{1}次重复以获得更好的统计信息\n”,
电话,
重复);
//线程
编写(“运行线程”);
结果:添加(新元组(“线程”,RunOnThreads(重复,调用));
Console.WriteLine();
//开始觉醒
Console.Write(“Running BeginInvoke”);
结果.添加(新元组(“BeginInvoke”,RunOnBeginInvoke(重复,调用));
Console.WriteLine();
//任务
Console.Write(“正在运行的任务”);
结果.添加(新元组(“任务”,runontask(重复,调用));
Console.WriteLine();
//线程池
Write(“运行线程池”);
结果:添加(新元组(“ThreadPool”、runnthreadpool(重复、调用));
Console.WriteLine();
Console.WriteLine();
//显示结果
results=results.OrderBy(rs=>rs.Item2.ToList();
foreach(结果中的var结果)
{
控制台写入线(
“{0}:在{1}ms avg中完成”,
结果.第1项,
(res
ThreadPool.QueueUserWorkItem(
x =>
{
try
{
// Do something
...
}
catch (Exception e)
{
// Log something
...
}
});
public class Program
{
public delegate void ThisDoesSomething();
// Perform a very simple operation to see the overhead of
// different async calls types.
public static void Main(string[] args)
{
const int repetitions = 25;
const int calls = 1000;
var results = new List<Tuple<string, double>>();
Console.WriteLine(
"{0} parallel calls, {1} repetitions for better statistics\n",
calls,
repetitions);
// Threads
Console.Write("Running Threads");
results.Add(new Tuple<string, double>("Threads", RunOnThreads(repetitions, calls)));
Console.WriteLine();
// BeginInvoke
Console.Write("Running BeginInvoke");
results.Add(new Tuple<string, double>("BeginInvoke", RunOnBeginInvoke(repetitions, calls)));
Console.WriteLine();
// Tasks
Console.Write("Running Tasks");
results.Add(new Tuple<string, double>("Tasks", RunOnTasks(repetitions, calls)));
Console.WriteLine();
// Thread Pool
Console.Write("Running Thread pool");
results.Add(new Tuple<string, double>("ThreadPool", RunOnThreadPool(repetitions, calls)));
Console.WriteLine();
Console.WriteLine();
// Show results
results = results.OrderBy(rs => rs.Item2).ToList();
foreach (var result in results)
{
Console.WriteLine(
"{0}: Done in {1}ms avg",
result.Item1,
(result.Item2 / repetitions).ToString("0.00"));
}
Console.WriteLine("Press a key to exit");
Console.ReadKey();
}
/// <summary>
/// The do stuff.
/// </summary>
public static void DoStuff()
{
Console.Write("*");
}
public static double RunOnThreads(int repetitions, int calls)
{
var totalMs = 0.0;
for (var j = 0; j < repetitions; j++)
{
Console.Write(".");
var toProcess = calls;
var stopwatch = new Stopwatch();
var resetEvent = new ManualResetEvent(false);
var threadList = new List<Thread>();
for (var i = 0; i < calls; i++)
{
threadList.Add(new Thread(() =>
{
// Do something
DoStuff();
// Safely decrement the counter
if (Interlocked.Decrement(ref toProcess) == 0)
{
resetEvent.Set();
}
}));
}
stopwatch.Start();
foreach (var thread in threadList)
{
thread.Start();
}
resetEvent.WaitOne();
stopwatch.Stop();
totalMs += stopwatch.ElapsedMilliseconds;
}
return totalMs;
}
public static double RunOnThreadPool(int repetitions, int calls)
{
var totalMs = 0.0;
for (var j = 0; j < repetitions; j++)
{
Console.Write(".");
var toProcess = calls;
var resetEvent = new ManualResetEvent(false);
var stopwatch = new Stopwatch();
var list = new List<int>();
for (var i = 0; i < calls; i++)
{
list.Add(i);
}
stopwatch.Start();
for (var i = 0; i < calls; i++)
{
ThreadPool.QueueUserWorkItem(
x =>
{
// Do something
DoStuff();
// Safely decrement the counter
if (Interlocked.Decrement(ref toProcess) == 0)
{
resetEvent.Set();
}
},
list[i]);
}
resetEvent.WaitOne();
stopwatch.Stop();
totalMs += stopwatch.ElapsedMilliseconds;
}
return totalMs;
}
public static double RunOnBeginInvoke(int repetitions, int calls)
{
var totalMs = 0.0;
for (var j = 0; j < repetitions; j++)
{
Console.Write(".");
var beginInvokeStopwatch = new Stopwatch();
var delegateList = new List<ThisDoesSomething>();
var resultsList = new List<IAsyncResult>();
for (var i = 0; i < calls; i++)
{
delegateList.Add(DoStuff);
}
beginInvokeStopwatch.Start();
foreach (var delegateToCall in delegateList)
{
resultsList.Add(delegateToCall.BeginInvoke(null, null));
}
// We lose a bit of accuracy, but if the loop is big enough,
// it should not really matter
while (resultsList.Any(rs => !rs.IsCompleted))
{
Thread.Sleep(10);
}
beginInvokeStopwatch.Stop();
totalMs += beginInvokeStopwatch.ElapsedMilliseconds;
}
return totalMs;
}
public static double RunOnTasks(int repetitions, int calls)
{
var totalMs = 0.0;
for (var j = 0; j < repetitions; j++)
{
Console.Write(".");
var resultsList = new List<Task>();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < calls; i++)
{
resultsList.Add(Task.Factory.StartNew(DoStuff));
}
// We lose a bit of accuracy, but if the loop is big enough,
// it should not really matter
while (resultsList.Any(task => !task.IsCompleted))
{
Thread.Sleep(10);
}
stopwatch.Stop();
totalMs += stopwatch.ElapsedMilliseconds;
}
return totalMs;
}
}