在C#中执行fire and forget方法的最简单方法是什么?

Mat*_*tin 134 .net c# nonblocking

我在WCF中看到他们有[OperationContract(IsOneWay = true)]属性.但是WCF似乎有点缓慢而且很重要,只是为了创建一个非阻塞功能.理想情况下会出现类似静态无效的阻塞MethodFoo(){},但我认为不存在.

在C#中创建非阻塞方法调用的最快方法是什么?

例如

class Foo
{
    static void Main()
    {
        FireAway(); //No callback, just go away
        Console.WriteLine("Happens immediately");
    }

    static void FireAway()
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:阅读此内容的每个人都应该考虑他们是否真的想要完成这个方法.(参见#2最佳答案)如果方法必须完成,那么在某些地方,比如ASP.NET应用程序,你需要做一些事情来阻止并保持线程活着.否则,这可能会导致"忘记但永远不会实际执行",在这种情况下,当然,根本不编写任何代码会更简单.(很好地描述了它在ASP.NET中的工作原理)

小智 254

ThreadPool.QueueUserWorkItem(o => FireAway());
Run Code Online (Sandbox Code Playgroud)

(五年后...)

Task.Run(() => FireAway());
Run Code Online (Sandbox Code Playgroud)

正如luisperezphd所指出的那样.

  • 考虑到这一点......在大多数应用程序中这很好,但在你的控制台应用程序将退出FireAway之前向控制台发送任何内容.ThreadPool线程是后台线程,并在应用程序死亡时死亡.另一方面,使用线程无法正常工作,因为在FireAway尝试写入窗口之前控制台窗口将消失. (13认同)
  • 无法保证运行非阻塞方法调用,因此这实际上是IMO问题的最准确答案.如果你需要保证执行,那么需要通过一个控制结构引入潜在的阻塞,例如AutoResetEvent(如Kev所提到的) (3认同)
  • 要在独立于线程池的线程中运行任务,我相信您也可以使用 `(new Action(FireAway)).BeginInvoke()` (2认同)
  • 怎么样`Task.Factory.StartNew(()=> myevent());`来自答案http://stackoverflow.com/questions/14858261/fire-event-on-background-thread (2认同)

Pat*_*ski 38

对于C#4.0及更新版本,让我觉得Ade Miller现在给出了最好的答案:在c#4.0中做一个火灾和遗忘方法的最简单方法

Task.Factory.StartNew(() => FireAway());
Run Code Online (Sandbox Code Playgroud)

甚至...

Task.Factory.StartNew(FireAway);
Run Code Online (Sandbox Code Playgroud)

要么...

new Task(FireAway).Start();
Run Code Online (Sandbox Code Playgroud)

哪里FireAway

public static void FireAway()
{
    // Blah...
}
Run Code Online (Sandbox Code Playgroud)

因此,凭借类和方法名称的简洁性,这取决于您选择的那个,使得线程池版本超过6到19个字符:)

ThreadPool.QueueUserWorkItem(o => FireAway());
Run Code Online (Sandbox Code Playgroud)

  • 我最感兴趣的是在好问题上轻松获得最佳答案.我肯定会鼓励其他人这样做. (11认同)
  • 根据http://stackoverflow.com/help/referencing,您需要使用blockquotes来指示您从其他来源引用.因为它是你的答案似乎是你自己的工作.您可以通过评论中的链接来重新发布答案的精确副本. (3认同)

Kev*_*Kev 17

要添加到Will的答案,如果这是一个控制台应用程序,只需在工作线程完成之前输入AutoResetEventa和a WaitHandle以防止它退出:

Using System;
Using System.Threading;

class Foo
{
    static AutoResetEvent autoEvent = new AutoResetEvent(false);

    static void Main()
    {
        ThreadPoolQueueUserWorkItem(new WaitCallback(FireAway), autoEvent);
        autoEvent.WaitOne(); // Will wait for thread to complete
    }

    static void FireAway(object stateInfo)
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
        ((AutoResetEvent)stateInfo).Set();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @dan_l - 不知道,为什么不把它作为一个新问题提出来并参考这个问题. (5认同)

Dav*_*och 17

对于.NET 4.5:

Task.Run(() => FireAway());
Run Code Online (Sandbox Code Playgroud)

  • Fyi,这主要是`Task.Factory.StartNew(()=> FireAway(),CancellationToken.None,TaskCreationOptions.DenyChildAttach,TaskScheduler.Default)的简称;`根据http://blogs.msdn.com/b/ pfxteam /存档/ 2011/10月24日/ 10229468.aspx (12认同)
  • 很高兴知道,@JimGeurts! (2认同)

Rob*_*les 15

一种简单的方法是使用无参数lambda创建和启动一个线程:

(new Thread(() => { 
    FireAway(); 
    MessageBox.Show("FireAway Finished!"); 
}) { 
    Name = "Long Running Work Thread (FireAway Call)",
    Priority = ThreadPriority.BelowNormal 
}).Start();
Run Code Online (Sandbox Code Playgroud)

通过在ThreadPool.QueueUserWorkItem上使用此方法,您可以命名新线程以便于调试.另外,不要忘记在例程中使用大量的错误处理,因为调试器之外的任何未处理的异常都会突然崩溃您的应用程序:

在此输入图像描述


Aug*_*eto 10

使用Asp.Net和.Net 4.5.2时,建议的方法是使用QueueBackgroundWorkItem.这是一个帮助类:

public static class BackgroundTaskRunner
{     
    public static void FireAndForgetTask(Action action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }

    /// <summary>
    /// Using async
    /// </summary>
    public static void FireAndForgetTask(Func<Task> action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                await action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

BackgroundTaskRunner.FireAndForgetTask(() =>
{
    FireAway();
});
Run Code Online (Sandbox Code Playgroud)

或使用异步:

BackgroundTaskRunner.FireAndForgetTask(async () =>
{
    await FireAway();
});
Run Code Online (Sandbox Code Playgroud)

这在Azure网站上很有用.

参考:使用QueueBackgroundWorkItem从.NET 4.5.2中的ASP.NET应用程序调度后台作业


小智 7

调用beginInvoke而不是捕获EndInvoke不是一个好方法.答案很简单:你应该调用EndInvoke的原因是因为在调用EndInvoke之前,.NET必须缓存调用的结果(即使没有返回值).例如,如果调用的代码抛出异常,则异常将缓存在调用数据中.在调用EndInvoke之前,它会保留在内存中.调用EndInvoke后,可以释放内存.对于这种特殊情况,内存可能会保留,直到进程关闭,因为数据是由调用代码在内部维护的.我想GC最终可能会收集它,但我不知道GC如何知道你已经放弃了数据而只是花了很长时间来检索它.我怀疑它确实如此.因此可能发生内存泄漏.

更多信息可以在http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx找到

  • 如果没有参考,GC可以判断什么时候放弃了.如果`BeginInvoke`返回一个包装器对象,该对象保存对包含实际结果数据的对象的引用,但未被引用,则包装器对象在放弃对它的所有引用后都有资格进行最终化. (3认同)

Ash*_*Ash 5

最简单的 .NET 2.0 及更高版本方法是使用异步编程模型(即委托上的 BeginInvoke):

static void Main(string[] args)
{
      new MethodInvoker(FireAway).BeginInvoke(null, null);

      Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);

      Thread.Sleep(5000);
}

private static void FireAway()
{
    Thread.Sleep(2000);

    Console.WriteLine("FireAway: " + Thread.CurrentThread.ManagedThreadId );  
}
Run Code Online (Sandbox Code Playgroud)


Osc*_*das 5

近十年后:

Task.Run(FireAway);
Run Code Online (Sandbox Code Playgroud)

我将在FireAway中添加异常处理和日志记录