您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > .NET

asp.net core源码如何实现监听Http请求,分析Kestrel看一下过程

时间:2022-10-04 19:42:43  来源:今日头条  作者:大好fish

#asp.net core#先让我们看一下最小API的代码,通过以下几行代码就可以搭建一个简单的asp.NET core web服务器,是不是十分简洁?

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

访问localhost:5001浏览器即可响应helloworld,那源码到底是如何实现http请求监听的呢?可以通过IIS,Kestrel实现,我们来看下Kestrel的实现,下图是实现的总体流程

整体运行图

让我们通过源码整体看一下执行过程,源码以.NET 7为例

1.WebApplication执行Run()方法

/// <summary>
/// Runs an application and block the calling thread until host shutdown.
/// </summary>
/// <param name="url">The URL to listen to if the server hasn't been configured directly.</param>
public void Run(string? url = null)
{
    Listen(url);
    HostingAbstractionsHostExtensions.Run(this);
}

2.
HostingAbstractionsHostExtensions执行RunAsync()方法

/// <summary>
    /// Runs an application and returns a <see cref="Task"/> that only completes when the token is triggered or shutdown is triggered.
    /// The <paramref name="host"/> instance is disposed of after running.
    /// </summary>
    /// <param name="host">The <see cref="IHost"/> to run.</param>
    /// <param name="token">The token to trigger shutdown.</param>
    /// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
    public static async Task RunAsync(this IHost host, CancellationToken token = default)
    {
        try
        {
            awAIt host.StartAsync(token).ConfigureAwait(false);
            await host.WaitForShutdownAsync(token).ConfigureAwait(false);
        }
        finally
        {
            if (host is IAsyncDisposable asyncDisposable)
            {
                await asyncDisposable.DisposeAsync().ConfigureAwait(false);
            }
            else
            {
                host.Dispose();
            }
        }
    }

3 KestrelServer执行StartAsync()方法

public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull
{
    return _innerKestrelServer.StartAsync(application, cancellationToken);
}

4.我们来看下Start方法,重点看
_transportManager.BindAsync方法

public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull
    {
        try
        {
            ValidateOptions();

            if (_hasStarted)
            {
                // The server has already started and/or has not been cleaned up yet
                throw new InvalidOperationException(CoreStrings.ServerAlreadyStarted);
            }
            _hasStarted = true;

            ServiceContext.Heartbeat?.Start();

            async Task OnBind(ListenOptions options, CancellationToken onBindCancellationToken)
            {
                var hasHttp1 = options.Protocols.HasFlag(HttpProtocols.Http1);
                var hasHttp2 = options.Protocols.HasFlag(HttpProtocols.Http2);
                var hasHttp3 = options.Protocols.HasFlag(HttpProtocols.Http3);
                var hasTls = options.IsTls;

                // Filter out invalid combinations.

                if (!hasTls)
                {
                    // Http/1 without TLS, no-op HTTP/2 and 3.
                    if (hasHttp1)
                    {
                        hasHttp2 = false;
                        hasHttp3 = false;
                    }
                    // Http/3 requires TLS. Note we only let it fall back to HTTP/1, not HTTP/2
                    else if (hasHttp3)
                    {
                        throw new InvalidOperationException("HTTP/3 requires HTTPS.");
                    }
                }

                // Quic isn't registered if it's not supported, throw if we can't fall back to 1 or 2
                if (hasHttp3 && _multiplexedTransportFactory is null && !(hasHttp1 || hasHttp2))
                {
                    throw new InvalidOperationException("This platform doesn't support QUIC or HTTP/3.");
                }

                // Disable adding alt-svc header if endpoint has configured not to or there is no
                // multiplexed transport factory, which happens if QUIC isn't supported.
                var addAltSvcHeader = !options.DisableAltSvcHeader && _multiplexedTransportFactory != null;

                var configuredEndpoint = options.EndPoint;

                // Add the HTTP middleware as the terminal connection middleware
                if (hasHttp1 || hasHttp2
                    || options.Protocols == HttpProtocols.None) // TODO a test fails because it doesn't throw an exception in the right place
                                                                // when there is no HttpProtocols in KestrelServer, can we remove/change the test?
                {
                    if (_transportFactory is null)
                    {
                        throw new InvalidOperationException($"Cannot start HTTP/1.x or HTTP/2 server if no {nameof(IConnectionListenerFactory)} is registered.");
                    }

                    options.UseHttpServer(ServiceContext, application, options.Protocols, addAltSvcHeader);
                    var connectionDelegate = options.Build();

                    // Add the connection limit middleware
                    connectionDelegate = EnforceConnectionLimit(connectionDelegate, Options.Limits.MaxConcurrentConnections, Trace);

                    options.EndPoint = await _transportManager.BindAsync(configuredEndpoint, connectionDelegate, options.EndpointConfig, onBindCancellationToken).ConfigureAwait(false);
                }

                if (hasHttp3 && _multiplexedTransportFactory is not null)
                {
                    // Check if a previous transport has changed the endpoint. If it has then the endpoint is dynamic and we can't guarantee it will work for other transports.
                    // For more details, see https://Github.com/dotnet/aspnetcore/issues/42982
                    if (!configuredEndpoint.Equals(options.EndPoint))
                    {
                        Trace.LogError(CoreStrings.DynamicPortOnMultipleTransportsNotSupported);
                    }
                    else
                    {
                        // 增加 ConnectionContext 中间件,后面处理Http请求会用到
                        options.UseHttp3Server(ServiceContext, application, options.Protocols, addAltSvcHeader);
                        var multiplexedConnectionDelegate = ((IMultiplexedConnectionBuilder)options).Build();

                        // Add the connection limit middleware
                        multiplexedConnectionDelegate = EnforceConnectionLimit(multiplexedConnectionDelegate, Options.Limits.MaxConcurrentConnections, Trace);
                        // 绑定
                        options.EndPoint = await _transportManager.BindAsync(configuredEndpoint, multiplexedConnectionDelegate, options, onBindCancellationToken).ConfigureAwait(false);
                    }
                }
            }

            AddressBindContext = new AddressBindContext(_serverAddresses, Options, Trace, OnBind);

            await BindAsync(cancellationToken).ConfigureAwait(false);
        }
        catch
        {
            // Don't log the error https://github.com/dotnet/aspnetcore/issues/29801
            Dispose();
            throw;
        }

        // Register the options with the event source so it can be logged (if necessary)
        KestrelEventSource.Log.AddServerOptions(Options);
    }

5 开始绑定socket端口

public async Task<EndPoint> BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig? endpointConfig, CancellationToken cancellationToken)
{
    if (_transportFactory is null)
    {
        throw new InvalidOperationException($"Cannot bind with {nameof(ConnectionDelegate)} no {nameof(IConnectionListenerFactory)} is registered.");
    }
    var transport = await _transportFactory.BindAsync(endPoint, cancellationToken).ConfigureAwait(false);
    StartAcceptLoop(new GenericConnectionListener(transport), c => connectionDelegate(c), endpointConfig);
    return transport.EndPoint;
}

6.注意下这种线程的写法

ThreadPool.UnsafeQueueUserWorkItem(StartAcceptingConnectionsCore, listener, preferLocal: false);

7 通过 while循环不断监听socket连接请求

private void StartAcceptingConnectionsCore(IConnectionListener<T> listener)
{
    // REVIEW: Multiple accept loops in parallel?
    _ = AcceptConnectionsAsync();

    async Task AcceptConnectionsAsync()
    {
        try
        {
            while (true)
            {
                var connection = await listener.AcceptAsync();

                if (connection == null)
                {
                    // We're done listening
                    break;
                }

                // Add the connection to the connection manager before we queue it for execution
                var id = _transportConnectionManager.GetNewConnectionId();
                var kestrelConnection = new KestrelConnection<T>(
                    id, _serviceContext, _transportConnectionManager, _connectionDelegate, connection, Log);

                _transportConnectionManager.AddConnection(id, kestrelConnection);

                Log.ConnectionAccepted(connection.ConnectionId);
                KestrelEventSource.Log.ConnectionQueuedStart(connection);

                ThreadPool.UnsafeQueueUserWorkItem(kestrelConnection, preferLocal: false);
            }
        }
        catch (Exception ex)
        {
            // REVIEW: If the accept loop ends should this trigger a server shutdown? It will manifest as a hang
            Log.LogCritical(0, ex, "The connection listener failed to accept any new connections.");
        }
        finally
        {
            _acceptLoopTcs.TrySetResult();
        }
    }
}

8 使用Socket AcceptAsync的方法

public async ValueTask<ConnectionContext?> AcceptAsync(CancellationToken cancellationToken = default)
{
    while (true)
    {
        try
        {
            Debug.Assert(_listenSocket != null, "Bind must be called first.");

            var acceptSocket = await _listenSocket.AcceptAsync(cancellationToken);

            // Only apply no delay to Tcp based endpoints
            if (acceptSocket.LocalEndPoint is IPEndPoint)
            {
                acceptSocket.NoDelay = _options.NoDelay;
            }

            return _factory.Create(acceptSocket);
        }
        catch (ObjectDisposedException)
        {
            // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done
            return null;
        }
        catch (SocketException e) when (e.SocketErrorCode == SocketError.OperationAborted)
        {
            // A call was made to UnbindAsync/DisposeAsync just return null which signals we're done
            return null;
        }
        catch (SocketException)
        {
            // The connection got reset while it was in the backlog, so we try again.
            SocketsLog.ConnectionReset(_logger, connectionId: "(null)");
        }
    }
}

9 当有连接过来后执行 KestrelConnection的Execute方法

internal async Task ExecuteAsync()
{
    var connectionContext = _transportConnection;

    try
    {
        KestrelEventSource.Log.ConnectionQueuedStop(connectionContext);

        Logger.ConnectionStart(connectionContext.ConnectionId);
        KestrelEventSource.Log.ConnectionStart(connectionContext);

        using (BeginConnectionScope(connectionContext))
        {
            try
            {
                await _connectionDelegate(connectionContext);
            }
            catch (Exception ex)
            {
                Logger.LogError(0, ex, "Unhandled exception while processing {ConnectionId}.", connectionContext.ConnectionId);
            }
        }
    }
    finally
    {
        await FireOnCompletedAsync();

        Logger.ConnectionStop(connectionContext.ConnectionId);
        KestrelEventSource.Log.ConnectionStop(connectionContext);

        // Dispose the transport connection, this needs to happen before removing it from the
        // connection manager so that we only signal completion of this connection after the transport
        // is properly torn down.
        await connectionContext.DisposeAsync();

        _transportConnectionManager.RemoveConnection(_id);
    }
}

10 监听到socket连接后,开始执行连接的委托,就是我们第4步注释提到的提前注入的一个中间件

public Task OnConnectionAsync(ConnectionContext connectionContext)
{
    var memoryPoolFeature = connectionContext.Features.Get<IMemoryPoolFeature>();
    var protocols = connectionContext.Features.Get<HttpProtocolsFeature>()?.HttpProtocols ?? _endpointDefaultProtocols;
    var localEndPoint = connectionContext.LocalEndPoint as IPEndPoint;
    var altSvcHeader = _addAltSvcHeader && localEndPoint != null ? HttpUtilities.GetEndpointAltSvc(localEndPoint, protocols) : null;

    var httpConnectionContext = new HttpConnectionContext(
        connectionContext.ConnectionId,
        protocols,
        altSvcHeader,
        connectionContext,
        _serviceContext,
        connectionContext.Features,
        memoryPoolFeature?.MemoryPool ?? System.Buffers.MemoryPool<byte>.Shared,
        localEndPoint,
        connectionContext.RemoteEndPoint as IPEndPoint);
    httpConnectionContext.Transport = connectionContext.Transport;

    var connection = new HttpConnection(httpConnectionContext);

    return connection.ProcessRequestsAsync(_application);
}

11 通过while循环监听Http请求,如果连接是keepAlive则不需要重复连接socket,一直在监听即可(如果是keeplive http连接不会断开)

private async Task ProcessRequests<TContext>(IHttpApplication<TContext> application) where TContext : notnull
    {
        while (_keepAlive)
        {
            if (_context.InitialExecutionContext is null)
            {
                // If this is a first request on a non-Http2Connection, capture a clean ExecutionContext.
                _context.InitialExecutionContext = ExecutionContext.Capture();
            }
            else
            {
                // Clear any AsyncLocals set during the request; back to a clean state ready for next request
                // And/or reset to Http2Connection's ExecutionContext giving access to the connection logging scope
                // and any other AsyncLocals set by connection middleware.
                ExecutionContext.Restore(_context.InitialExecutionContext);
            }

            BeginRequestProcessing();

            var result = default(ReadResult);
            bool endConnection;
            do
            {
                if (BeginRead(out var awaitable))
                {
                    result = await awaitable;
                }
            } while (!TryParseRequest(result, out endConnection));

            if (endConnection)
            {
                // Connection finished, stop processing requests
                return;
            }

            var messageBody = CreateMessageBody();
            if (!messageBody.RequestKeepAlive)
            {
                _keepAlive = false;
            }

            IsUpgradableRequest = messageBody.RequestUpgrade;

            InitializeBodyControl(messageBody);

            var context = application.CreateContext(this);

            try
            {
                KestrelEventSource.Log.RequestStart(this);

                // Run the application code for this request
                await application.ProcessRequestAsync(context);

                // Trigger OnStarting if it hasn't been called yet and the app hasn't
                // already failed. If an OnStarting callback throws we can go through
                // our normal error handling in ProduceEnd.
                // https://github.com/aspnet/KestrelHttpServer/issues/43
                if (!HasResponseStarted && _applicationException == null && _onStarting?.Count > 0)
                {
                    await FireOnStarting();
                }

                if (!_connectionAborted && !VerifyResponseContentLength(out var lengthException))
                {
                    ReportApplicationError(lengthException);
                }
            }
            catch (BadHttpRequestException ex)
            {
                // Capture BadHttpRequestException for further processing
                // This has to be caught here so StatusCode is set properly before disposing the HttpContext
                // (DisposeContext logs StatusCode).
                SetBadRequestState(ex);
                ReportApplicationError(ex);
            }
            catch (Exception ex)
            {
                ReportApplicationError(ex);
            }

            KestrelEventSource.Log.RequestStop(this);

            // At this point all user code that needs use to the request or response streams has completed.
            // Using these streams in the OnCompleted callback is not allowed.
            try
            {
                Debug.Assert(_bodyControl != null);
                await _bodyControl.StopAsync();
            }
            catch (Exception ex)
            {
                // BodyControl.StopAsync() can throw if the PipeWriter was completed prior to the application writing
                // enough bytes to satisfy the specified Content-Length. This risks double-logging the exception,
                // but this scenario generally indicates an app bug, so I don't want to risk not logging it.
                ReportApplicationError(ex);
            }

            // 4XX responses are written by TryProduceInvalidRequestResponse during connection tear down.
            if (_requestRejectedException == null)
            {
                if (!_connectionAborted)
                {
                    // Call ProduceEnd() before consuming the rest of the request body to prevent
                    // delaying clients waiting for the chunk terminator:
                    //
                    // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663
                    //
                    // This also prevents the 100 Continue response from being sent if the app
                    // never tried to read the body.
                    // https://github.com/aspnet/KestrelHttpServer/issues/2102
                    //
                    // ProduceEnd() must be called before _application.DisposeContext(), to ensure
                    // HttpContext.Response.StatusCode is correctly set when
                    // IHttpContextFactory.Dispose(HttpContext) is called.
                    await ProduceEnd();
                }
                else if (!HasResponseStarted)
                {
                    // If the request was aborted and no response was sent, there's no
                    // meaningful status code to log.
                    StatusCode = 0;
                }
            }

            if (_onCompleted?.Count > 0)
            {
                await FireOnCompleted();
            }

            application.DisposeContext(context, _applicationException);

            // Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
            if (!_connectionAborted && _requestRejectedException == null && !messageBody.IsEmpty)
            {
                await messageBody.ConsumeAsync();
            }

            if (HasStartedConsumingRequestBody)
            {
                await messageBody.StopAsync();
            }
        }
    }

 

11 处理Http请求中间再执行我们在Http管道里的委托方法,用来进行HTTP的构造和HTTP Response,管道这里就不展开介绍了,可以看下官网的介绍

 

 

12 最后通过Http1OutputProducer最终响应结果

public ValueTask<FlushResult> WriteStreamSuffixAsync()
{
    ValueTask<FlushResult> result = default;

    lock (_contextLock)
    {
        if (!_writeStreamSuffixCalled)
        {
            if (_autoChunk)
            {
                var writer = new BufferWriter<PipeWriter>(_pipeWriter);
                result = WriteAsyncInternal(ref writer, EndChunkedResponseBytes);
            }
            else if (_unflushedBytes > 0)
            {
                result = FlushAsync();
            }

            _writeStreamSuffixCalled = true;
        }
    }

    return result;
}

 





Tags:.net   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
作为 .NET 开发人员,我为什么开始使用 Python?
作者 | Alex Maher编译 | 小欧作为一名 .NET 开发人员,很长一段时间以来,我一直关注 C# 和 .NET 的出色工具和功能。但我最近开始使用 Python,感觉非常棒。这里申明一点,这篇文...【详细内容】
2023-12-25  Search: .net  点击:(143)  评论:(0)  加入收藏
.NET Core 3.1 升级到 .NET 8,看看都有哪些变化
.NET Core 3.1 已经用了很长一段时间,其实在 2022 年的年底微软已经不提供支持了,后面的一个 LTS 版本 .NET 6 也会在 2024 年 11 月终止支持,所以直接升级到 .NET 8 是最好的...【详细内容】
2023-12-08  Search: .net  点击:(192)  评论:(0)  加入收藏
.NET Core的中间件来对Web API进行流量限制实现方法
在.NET Core中,我们可以使用ASP.NET Core的中间件来对Web API进行流量限制。ASP.NET Core提供了一个名为RateLimit的开源库,可以方便地实现流量限制功能。下面将详细介绍如何...【详细内容】
2023-12-06  Search: .net  点击:(177)  评论:(0)  加入收藏
深入 .NET 异步编程:Task 与 ValueTask 的区别与选择
在 .NET 中,Task 和 ValueTask 都是用于表示异步操作的类型,但它们有一些重要的区别。TaskTask 是最常见的表示异步操作的类型。它通常用于表示耗时的、异步的操作,比如从文件...【详细内容】
2023-11-26  Search: .net  点击:(190)  评论:(0)  加入收藏
.NET 8.0中有哪些新的变化?
作者:葡萄城技术团队链接:https://my.oschina.net/powertoolsteam/blog/10143465.NET 8已正式GA1 性能提升.NET 8 在整个堆栈中带来了数千项性能改进 。默认情况下会启用一种...【详细内容】
2023-11-20  Search: .net  点击:(223)  评论:(0)  加入收藏
.NET Core下有热门的ORM框架使用方法
.NET Core下有很多热门的ORM框架,以下是其中六个常用的框架,包括EF Core、Dapper、NHibernate、Fluent NHibernate、LLBLGen Pro和PetaPoco。接下来,我将为您详细介绍每个框架...【详细内容】
2023-11-16  Search: .net  点击:(161)  评论:(0)  加入收藏
.NET Core中一些优秀的项目和框架
.NET Core是一个跨平台的开源框架,它可以用来构建Web应用程序、微服务、桌面应用程序和游戏等。它具有高性能、可扩展性和安全性等优点,因此越来越多的企业和开发者选择使用.N...【详细内容】
2023-11-13  Search: .net  点击:(212)  评论:(0)  加入收藏
.net core下优秀的日志框架使用解析,附源代码
在 .NET Core 中,日志是一个非常重要的组件,它可以帮助我们记录应用程序的运行情况,以便在出现问题时进行排查。在本文中,我们将介绍五个优秀的 .NET Core 日志框架,它们分别是...【详细内容】
2023-10-18  Search: .net  点击:(286)  评论:(0)  加入收藏
2 个 .NET 操作的 Redis 客户端类库
Redis ,是一个高性能(NOSQL)的key-value数据库,Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redi...【详细内容】
2023-10-11  Search: .net  点击:(245)  评论:(0)  加入收藏
记一次 .NET 某拍摄监控软件的卡死分析
一:背景1. 讲故事今天本来想写一篇 非托管泄露 的生产事故分析,但想着昨天就上了一篇非托管文章,连着写也没什么意思,换个口味吧,刚好前些天有位朋友也找到我,说他们的拍摄监控软...【详细内容】
2023-09-27  Search: .net  点击:(234)  评论:(0)  加入收藏
▌简易百科推荐
.Net开发中十种常见的内存错误以及相应的解决方案
在.Net开发中,为内存管理方面提供了许多便利,但仍然存在一些常见的错误和陷阱。这些错误可能导致内存泄漏、性能下降、异常抛出等问题,严重影响应用程序的稳定性和性能。在软件...【详细内容】
2024-03-26  小乖兽技术  今日头条  Tags:.Net   点击:(13)  评论:(0)  加入收藏
.NET配置文件大揭秘:轻松读取JSON、XML、INI和环境变量
概述:.NET中的IConfiguration接口提供了一种多源读取配置信息的灵活机制,包括JSON、XML、INI文件和环境变量。通过示例,清晰演示了从这些不同源中读取配置的方法,使配置获取变得...【详细内容】
2023-12-28  架构师老卢  今日头条  Tags:.NET   点击:(92)  评论:(0)  加入收藏
.Net开发都应当掌握的泛型基础知识集合
在C#编程语言中,泛型是一项非常强大和重要的功能。它允许我们编写更加灵活和通用的代码,同时提高代码的可重用性和性能。本文将介绍C#泛型的一些关键知识点,帮助读者理解和应用...【详细内容】
2023-12-25  小乖兽技术  今日头条  Tags:.Net   点击:(125)  评论:(0)  加入收藏
作为 .NET 开发人员,我为什么开始使用 Python?
作者 | Alex Maher编译 | 小欧作为一名 .NET 开发人员,很长一段时间以来,我一直关注 C# 和 .NET 的出色工具和功能。但我最近开始使用 Python,感觉非常棒。这里申明一点,这篇文...【详细内容】
2023-12-25    51CTO  Tags:.NET   点击:(143)  评论:(0)  加入收藏
.NET Core 3.1 升级到 .NET 8,看看都有哪些变化
.NET Core 3.1 已经用了很长一段时间,其实在 2022 年的年底微软已经不提供支持了,后面的一个 LTS 版本 .NET 6 也会在 2024 年 11 月终止支持,所以直接升级到 .NET 8 是最好的...【详细内容】
2023-12-08  不止dotNET  微信公众号  Tags:.NET   点击:(192)  评论:(0)  加入收藏
.NET Core的中间件来对Web API进行流量限制实现方法
在.NET Core中,我们可以使用ASP.NET Core的中间件来对Web API进行流量限制。ASP.NET Core提供了一个名为RateLimit的开源库,可以方便地实现流量限制功能。下面将详细介绍如何...【详细内容】
2023-12-06  架构师老卢  今日头条  Tags:.NET   点击:(177)  评论:(0)  加入收藏
微软官方出品微服务架构:十个.Net开源项目
1、一个高性能类型安全的.NET枚举实用开源库Enums.NET是一个.NET枚举实用程序库,专注于为枚举提供丰富的操作方法。它支持.NET Framework和.Net Core。它主要优点表现在类型...【详细内容】
2023-12-06  编程乐趣  今日头条  Tags:.Net   点击:(133)  评论:(0)  加入收藏
.NET开源的处理分布式事务的解决方案
前言在分布式系统中,由于各个系统服务之间的独立性和网络通信的不确定性,要确保跨系统的事务操作的最终一致性是一项重大的挑战。今天给大家推荐一个.NET开源的处理分布式事务...【详细内容】
2023-11-30  追逐时光者  微信公众号  Tags:.NET   点击:(173)  评论:(0)  加入收藏
深入 .NET 异步编程:Task 与 ValueTask 的区别与选择
在 .NET 中,Task 和 ValueTask 都是用于表示异步操作的类型,但它们有一些重要的区别。TaskTask 是最常见的表示异步操作的类型。它通常用于表示耗时的、异步的操作,比如从文件...【详细内容】
2023-11-26  架构师老卢  微信公众号  Tags: .NET   点击:(190)  评论:(0)  加入收藏
.NET字符串存储:解析常量与动态字符串,深入了解内存机制
在 .NET 中,字符串是不可变的,这意味着一旦创建,字符串的内容就不能被修改。字符串在内存中以不同的方式存储,具体取决于它是常量字符串还是动态创建的字符串。常量字符串常量字...【详细内容】
2023-11-25  架构师老卢  微信公众号  Tags:.NET   点击:(196)  评论:(0)  加入收藏
站内最新
站内热门
站内头条