问完这个问题后,我在使用异步时感觉很舒服 ASP.NET MVC中的操作.因此,我就此写了两篇博客文章:

我对ASP上的异步操作有太多误解.NET MVC.

我总是听到这句话:Application can scale better if operations run asynchronously

我也经常听到这样的句子:if you have a huge volume of traffic, you may be better off not performing your queries asynchronously - consuming 2 extra threads to service one request takes resources away from other incoming requests.

我认为这两句话前后矛盾.

我没有太多关于threadpool如何在ASP上工作的信息.NET但我知道线程池的线程大小有限.所以,第二句话必须与这个问题有关.

我想知道ASP中是否存在异步操作.NET MVC使用的线程来自上的ThreadPool.网络4?

例如,当我们实现AsyncController时,应用程序是如何构造的?如果我获得了巨大的流量,实现AsyncController是个好主意吗?

有没有人能在我眼前把这个黑幕拉开,向我解释ASP上的异步协议.网络MVC 3(网络4)?

Edit:个个

我已经阅读了以下文件近数百次,我理解主要交易,但我仍然感到困惑,因为有太多不一致的 comments .

Using an Asynchronous Controller in ASP.NET MVC

Edit:个个

假设我有如下控制器操作(但不是AsyncController的实现):

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

正如你在这里看到的,我发动了一次行动,却忘了它.然后,我立即返回,不必等待它完成.

在这种情况下,是否必须使用线程池中的线程?如果是这样,那么在它完成之后,该线程会发生什么情况呢?GC是刚完工就进来清理的吗?

Edit:个个

对于@Darin的答案,下面是一个与数据库对话的异步代码示例:

public class FooController : AsyncController {

    //EF 4.2 DbContext instance
    MyContext _context = new MyContext();

    public void IndexAsync() { 

        AsyncManager.OutstandingOperations.Increment(3);

        Task<IEnumerable<Foo>>.Factory.StartNew(() => { 

           return 
                _context.Foos;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foos"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<Bars>>.Factory.StartNew(() => { 

           return 
                _context.Bars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["bars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<FooBar>>.Factory.StartNew(() => { 

           return 
                _context.FooBars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foobars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    public ViewResult IndexCompleted(
        IEnumerable<Foo> foos, 
        IEnumerable<Bar> bars,
        IEnumerable<FooBar> foobars) {

        //Do the regular stuff and return

    }
}

推荐答案

下面是我建议大家阅读的excellent article篇文章,以便更好地理解ASP中的异步处理.NET(异步控制器基本上就是这样表示的).

让我们首先考虑一个标准的同步动作:

public ActionResult Index()
{
    // some processing
    return View();
}

当对该操作发出请求时,将从线程池中提取一个线程,并在该线程上执行该操作的主体.因此,如果此操作中的处理速度较慢,则在整个处理过程中都会阻塞此线程,因此无法重用此线程来处理其他请求.在请求执行结束时,线程返回到线程池.

现在让我们举一个异步模式的例子:

public void IndexAsync()
{
    // perform some processing
}

public ActionResult IndexCompleted(object result)
{
    return View();
}

当向索引操作发送请求时,将从线程池中提取一个线程,并执行IndexAsync方法的主体.一旦这个方法的主体完成执行,线程就会返回到线程池.然后,使用标准AsyncManager.OutstandingOperations,一旦发出异步操作完成的信号,就会从线程池中提取另一个线程,并对其执行IndexCompleted操作的主体,并将结果呈现给客户端.

因此,我们可以在这个模式中看到,单个客户端HTTP请求可以由两个不同的线程执行.

现在有趣的部分发生在IndexAsync方法中.如果在其中有一个阻塞操作,那么就完全浪费了异步控制器的全部功能,因为您正在阻塞工作线程(请记住,此操作的主体是在从线程池中提取的线程上执行的).

你可能会问,我们什么时候才能真正利用异步控制器呢?

当我们进行I/O密集型操作(例如数据库和远程服务的网络调用)时,我们可以获得最大yield .如果你有一个CPU密集型的操作,异步操作不会给你带来多少好处.

那么,为什么我们可以从I/O密集型操作中获益呢?因为我们可以用I/O Completion Ports美元.IOCP非常强大,因为您在整个操作执行过程中不会消耗服务器上的任何线程或资源.

它们是怎么工作的?

假设我们想要使用WebClient.DownloadStringAsync方法下载远程网页的内容.您调用这个方法,它将在操作系统中注册IOCP,并立即返回.在处理整个请求的过程中,服务器上不会消耗任何线程.一切都发生在远程服务器上.这可能需要很多时间,但你不在乎,因为你没有危及你的工作线程.一旦收到响应,IOCP就会发出信号,从线程池中提取一个线程,并在此线程上执行回调.但正如你所看到的,在整个过程中,我们并没有垄断任何线程.

FileStream等方法也是如此.开始,SqlCommand.开始执行...

并行多个数据库调用怎么样?假设您有一个同步控制器操作,其中您按顺序执行了4次阻塞数据库调用.很容易计算出,如果每个数据库调用需要200毫秒,那么控制器操作将需要大约800毫秒来执行.

如果不需要按顺序运行这些调用,并行化它们会提高性能吗?

这是一个很大的问题,不容易回答.也许问,也许不问.这将完全取决于您如何实现这些数据库调用.如果您像前面讨论的那样使用异步控制器和I/O完成端口,您将提高此控制器操作以及其他操作的性能,因为您不会独占工作线程.

另一方面,如果您实现得很差(在线程池中的线程上执行阻塞数据库调用),您基本上会将此操作的总执行时间降低到大约200ms,但是您将消耗4个工作线程,因此您可能会降低其他请求的性能,这些请求可能会因为池中缺少处理它们的线程而变得饥饿.

因此,这是非常困难的,如果您还没有准备好对应用程序执行广泛的测试,请不要实现异步控制器,因为这样做可能会带来更多的损害而不是好处.只有在有理由的情况下才能实现它们:例如,您已经确定标准的同步控制器操作是应用程序的瓶颈(当然,在执行了大量的负载测试和测量之后).

现在让我们考虑一下您的示例:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

当收到索引操作的请求时,从线程池中提取一个线程来执行其主体,但其主体仅使用TPL调度一个新任务.因此,操作执行结束,线程返回到线程池.除此之外,TPL使用线程池中的线程来执行其处理.因此,即使原始线程返回到线程池,您也从该池中提取了另一个线程来执行任务体.所以你已经 destruct 了你宝贵的游泳池中的两条线.

现在让我们考虑以下几点:

public ViewResult Index() { 

    new Thread(() => { 
        //Do an advanced looging here which takes a while
    }).Start();

    return View();
}

在本例中,我们手动派生线程.在这种情况下,Index操作体的执行时间可能会稍长一些(因为派生新线程比从现有池中提取线程更昂贵).但是高级日志(log)记录操作的执行将在不属于池的线程上完成.因此,我们不会危及池中的线程,这些线程仍处于空闲状态,可以为其他请求提供服务.

.net相关问答推荐

是否必须使用 Visual Studio 预览才能使用 MAUI?

如何在 Windows 窗体上显示 ClickOnce 版本号

如何将 Assembly.CodeBase 转换为 C# 中的文件系统路径?

如何确定计时器是否正在运行?

类似于字典但没有值的 C# 数据 struct

将字符串与容差进行比较

string.Format 如何处理空值?

C#6.0 字符串插值本地化

整个命名空间的SuppressMessage

AutoMapper 的替代品

如何 Select 数据表中列的最小值和最大值?

如何在 C# 中打开 Excel 文件?

C#:内存不足异常

为什么使用 ImmutableList 而不是 ReadOnlyCollection?

是否有 Linq 方法可以将单个项目添加到 IEnumerable

在 .NET 中计算目录大小的最佳方法是什么?

从 'System.Int32' 到 'System.Nullable`1[[System.Int32, mscorlib]] 的无效转换

为什么甚至可以更改私有成员,或使用反射在 C# 中运行私有方法?

Guid.Parse() 或 new Guid() - 有什么区别?

如何将两个 List 相互比较?