在C#中最简单的方法来解决问题?


150

我在WCF中看到它们具有[OperationContract(IsOneWay = true)]属性。但是WCF似乎只是为了创建一个非阻塞函数而缓慢而繁重。理想情况下,会出现诸如static void nonblocking之类的东西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");
    }
}

注意:阅读此书的每个人都应该考虑他们是否真的希望该方法完成。(请参阅#2最佳答案)如果该方法必须完成,则在某些地方(例如ASP.NET应用程序),您将需要执行某些操作来阻止线程并使线程保持活动状态。否则,这可能会导致“忘却却从未真正执行”,在这种情况下,完全不编写任何代码当然会更简单。(很好地描述了它如何在ASP.NET中工作

Answers:


273
ThreadPool.QueueUserWorkItem(o => FireAway());

(五年后...)

Task.Run(() => FireAway());

正如luisperezphd所指出的。


你赢了。不,实际上,这似乎是最短的版本,除非将其封装到另一个函数中。
OregonGhost

13
考虑一下...在大多数应用程序中,这很好,但是在您的控制台中,FireAway将任何内容发送到控制台之前,控制台应用程序将退出。ThreadPool线程是后台线程,在应用程序终止时终止。另一方面,使用线程也不起作用,因为控制台窗口会在FireAway尝试写入窗口之前消失。

3
没有任何方法可以确保运行非阻塞方法,因此实际上这是对IMO问题的最准确答案。如果您需要保证执行,则需要通过诸如AutoResetEvent(如Kev提到的)之类的控制结构引入潜在的阻塞
Guvante

1
要在独立于线程池的线程中运行任务,我相信您也可以这样做(new Action(FireAway)).BeginInvoke()
Sam Harwell 2009年

2
Task.Factory.StartNew(() => myevent());从答案stackoverflow.com/questions/14858261/…
Luis Perez 2014年

39

对于C#4.0和更高版本,让我惊讶的是,Ade Miller现在在这里给出了最佳答案:在C#4.0中执行射击和忘记方法的最简单方法

Task.Factory.StartNew(() => FireAway());

甚至...

Task.Factory.StartNew(FireAway);

要么...

new Task(FireAway).Start();

哪里FireAway

public static void FireAway()
{
    // Blah...
}

因此,凭借类和方法名的简洁性,这取决于选择的字符集,使线程池版本比六个字符多十九个字符:)

ThreadPool.QueueUserWorkItem(o => FireAway());

11
我对在好问题上轻松获得最佳答案最感兴趣。我绝对会鼓励其他人也这样做。
Patrick Szalapski 2014年

3
根据stackoverflow.com/help/referencing,您需要使用blockquotes来表示您正在引用其他来源。因为它的答案似乎是您自己的工作。通过重新发布答案的精确副本来实现您的意图,可以使用评论中的链接来实现。
汤姆·雷德芬

1
如何将参数传递给FireAway
GuidoG

我相信您必须使用第一个解决方案:Task.Factory.StartNew(() => FireAway(foo));
Patrick Szalapski

1
谨慎同时使用Task.Factory.StartNew(() => FireAway(foo));FOO不能在循环修改为解释这里这里
AAA

22

对于.NET 4.5:

Task.Run(() => FireAway());

15
Fyi,Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);根据blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim Geurts

2
很高兴知道,@ JimGeurts!
大卫·默多克

为了后代的利益,在评论中链接为@JimGeurts的文章现在为404(感谢MS!)。鉴于该URL的邮戳,本文由Stephen Toub似乎是正确的- devblogs.microsoft.com/pfxteam/...
丹·阿特金森

1
@DanAtkinson你是正确的。这是在archive.org原来,以防万一MS再次移动它:web.archive.org/web/20160226022029/http://blogs.msdn.com/b/...
大卫默多克

18

要添加到Will的答案中(如果这是一个控制台应用程序),只需在工作线程完成之前抛出an AutoResetEvent和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();
    }
}

如果这不是控制台应用程序,并且我的C#类是COM Visible,那么您的AutoEvent是否可以使用?autoEvent.WaitOne()是否阻塞?
dan_l 2012年

5
@dan_l-不知道,为什么不问这个新问题并引用这个问题。
凯夫2012年

@dan_l,是的,WaitOne它始终会阻塞并且AutoResetEvent将始终起作用
AaA

如果只有一个后台线程,则AutoResetEvent仅在这里真正起作用。我想知道使用多个线程时屏障是否会更好。docs.microsoft.com/zh-CN/dotnet/standard/threading/barrier
Martin Brown

@MartinBrown-不确定是真的。您可以有一个数组WaitHandle,每个都有自己的,AutoResetEvent并且有一个ThreadPool.QueueUserWorkItem。在那之后WaitHandle.WaitAll(arrayOfWaitHandles)
Kev

15

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

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

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

在此处输入图片说明


当由于长时间运行或阻塞过程而需要专用线程时,+ 1。
保罗·特纳

12

在使用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
            }
        });
    }
}

用法示例:

BackgroundTaskRunner.FireAndForgetTask(() =>
{
    FireAway();
});

或使用异步:

BackgroundTaskRunner.FireAndForgetTask(async () =>
{
    await FireAway();
});

这在Azure网站上效果很好。

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


7

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

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


3
如果没有对它们的引用,GC可以告知何时将其丢弃。如果BeginInvoke返回一个包装对象,该包装对象保留了保存实际结果数据的对象的引用,但未被该对象引用,则一旦放弃对该对象的所有引用,包装对象将有资格进行最终确定。
2012年

我质疑这不是“一个好方法”。用您关注的错误条件进行测试,看看是否有问题。即使垃圾回收不是完美的,它也可能不会明显影响大多数应用程序。对于Microsoft,“如有必要,您可以调用EndInvoke从委托中检索返回值,但这不是必需的。EndInvoke将阻塞,直到可以检索到返回值为止。” 来自:msdn.microsoft.com/zh-cn/library/0b1bf3y3(v = vs.90).aspx
Abacus

5

近十年后:

Task.Run(FireAway);

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


3

最简单的.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 );  
}

Task.Run()存在之前,这可能是最简洁的方法。
宾基
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.