2024年10月

在我们团队的开发历程中,C# 和 .NET 框架一直是我们的主力语言,伴随我们走过了无数个项目。当微软推出 Blazor 这一革命性的框架时,我们对其充满了期待。Blazor 以其优良的架构和微软的强大背书,似乎预示着前端开发的新纪元。我们希望借助 Blazor 的优势,快速构建与后台服务配套的前端应用。然而,随着开发的深入,我们发现这条路并不如预期顺利,最终不得不放弃 Blazor,转而使用其他 Web 技术。本文将分享我们的经历,希望能为其他团队提供参考。

1. 初识 Blazor 全栈 UI:满怀期待

Blazor 的出现,让我们看到了使用 C# 进行前端开发的可能性。对于习惯了 .NET 环境的开发者来说,这无疑是个好消息。我们可以在不切换语言的情况下,编写前端代码,提升了开发效率和代码一致性。

Blazor 全栈 UI 的技术架构优势明显:

  1. 统一的开发体验
    :开发者可以使用相同的 C# 代码和 Razor 组件在 Blazor Server 和 Blazor WebAssembly 之间进行无缝切换,简化了多种应用类型的开发流程。
  2. 灵活的应用架构
    :Blazor Web App 可以根据具体需求灵活选择 Blazor Server 或 Blazor WebAssembly。
    • Blazor Server 通过 SignalR 技术提供服务端和客户端的实时数据传输,和较快的初始加载时间。
    • 而 Blazor WebAssembly 可以在浏览器中高效运行,允许用户离线使用,减少服务器负担,提高用户的交互体验。
    • 支持灵活地选择 Server 还是 WASM 进行渲染,可以针对整个应用程序、某个页面或控件设置渲染模式。
    • 可以将 Server 搭建为 Backend-for-Frontend,写后台接口访问数据库。WASM 搭建为 Frontend,编写布局和页面。达到前后端分离,前端的页面元素渲染和交互不再依赖后端,只有调用 API 获取数据需要后端。
  3. 增强的性能
    :.NET 8 中的 Blazor 具有更好的性能优化,包括更快的组件渲染和数据传输,提升了用户体验。通过加强导航(Enhanced Navigation)功能,使用户在交互和导航时感觉在浏览 SPA 网站。
  4. 利用渲染树构建组件
    :提供了 ChildContent、ChildFragment 来灵活地自定义控件元素的样式和行为,支持通过模板重写控件内部的元素。
  5. 用户身份和权限认证
    :利用 Identity 无缝便捷地集成用户验证和权限控制,支持对某个页面、控件、Controller,API 设置访问权限。WASM 应用缓存了属于客户端的数据,无需在服务端维护,例如用户身份信息、登录状态等等。
  6. 熟悉的开发工具
    :Visual Studio 和其他 IDE 对 Blazor Web App 的友好支持,提供更好的调试体验和开发效率。

2. 遭遇挑战:理想与现实的差距

然而,理想很丰满,现实却很骨感。在项目推进过程中,我们逐渐感受到 Blazor 的局限性。以下是我们遇到的一些主要问题:

  1. UI 组件库有限
    :相比于 React、Vue 等成熟框架,虽然 Blazor 社区在不断发展,但现有的 UI 组件库仍然相对较少。并且,面对某些复杂的前端效果,我们发现 Blazor 难以将不同组件库中的组件融合在一起使用,库之间很难做到样式和操作的统一,这严重影响了我们的开发效率。
  2. 前端效果难以实现
    :Blazor 在处理某些前端效果时显得力不从心。由于其运行机制的特殊性,一些在其他框架中轻松实现的动画、交互效果,在 Blazor 中却需要绕过各种限制,利用 JS 互操作来完成。这不仅降低了开发效率,也影响了用户体验。
  3. 社区支持不足
    :一个框架的生态环境对于其发展至关重要。我们发现,当遇到问题时,几乎无法在社区中找到解决方案。一些无法通过官方文档解决的疑问,StackOverflow 或 GitHub 上的相关帖子寥寥无几。这使得我们在排查和解决问题时陷入困境。

3. 展望未来:Blazor 的前景并不明朗

  1. 缺乏实际应用案例
    :尽管 Blazor 在技术上有许多优点并受到微软的支持,但目前为止,使用 Blazor 搭建网站的知名企业、公司或组织仍然相对稀少。这使得潜在用户对其稳定性和长期可维护性产生疑虑。
  2. 微软的转向
    :更让人担忧的是,微软似乎不再为 Blazor 投入足够的资源。近年来,微软的重心逐渐转移到人工智能(AI)领域,对 Blazor 的更新和支持力度明显下降。这让 Blazor 的未来充满了不确定性。

4. 做出抉择:从 Blazor 回到 Vue

经过一段时间的开发,我们团队意识到,Blazor 可能并不适合我们未来的产品路线。尽管我们喜欢用 C# 开发,但在 Web 开发领域,其他技术栈在组件丰富性、社区支持和生态系统上显然更具优势。

综合以上因素,我们不得不做出艰难的决定:

  • 停止在新产品中使用 Blazor
    :为了项目的长期发展,我们决定在下一个产品中放弃使用 Blazor。
  • 重构现有产品的前端部分
    :对于已经使用 Blazor 的产品,我们计划在第二个版本中重构前端,改用更成熟的 Web 技术,如 Vue。

这一决策虽然艰难,但也是我们经过深思熟虑后的选择。

5. 反思与建议:Blazor 的适用场景

通过这次经历,我们发现选用 Blazor 可以从以下几点因素去考量:

  1. 团队规模
    :Blazor 更适合单兵作战的开发者,或 2 至 4 人的小团队。这些开发者通常具有较强的 C# 和 .NET 背景,能够快速上手,学习 Blazor 的负担相对其他非 .NET 背景的开发者要小很多,可以利用现有的 .NET 技能快速构建和部署小型项目。
  2. 应用类型
    :Blazor 非常适合用于构建功能相对简单的标准管理系统,比如内部管理工具,进行数据录入、生成报告、利用仪表盘和报表进行数据展示。这些应用通常不需要复杂的前端交互,且需求相对稳定,可以利用 Blazor 的组件化特性快速搭建。
  3. 产品演化
    :Blazor 更适合那些在开发初期对功能和用户界面需求有明确设定的产品。这种情况下,可以在产品发布后无需过多的担心产品后期的接手和维护问题。减少产品变更和迭代所带来的维护成本。

6. 结语

在这个快速发展的技术世界里,选择一个合适的开发框架至关重要。Blazor 的理念值得肯定,但在当前阶段,可能还不适合用于大型商业项目。我们的经验教训也提醒着其他团队,在技术选择上要更加谨慎和前瞻。

未来,我们仍会关注 Blazor 的发展,但在那之前,我们将选择更适合当前需求的技术方案。尽管我们与 Blazor 的旅程暂告一段落,但这段经历将成为我们继续探索和学习的宝贵财富。

Disclaimer 声明:本文由 AI 辅助完成撰写

技术背景

在前面的两篇博客中,我们分别介绍了
Ewald算法求解静电势能

基于格点拉格朗日插值法的PME算法
。在多种计算优化算法(Ewald求和、快速傅里叶变换、格点拉格朗日插值、截断近似)的加持下,使得我们不需要在实空间进行大量的迭代,也可以得到一个近似收敛的静电势能结果。相关的PME计算公式为:

\[\begin{align*}
E&=E^S+E^L-E^{self}\\
&=\sum_{i,j\in \{Neigh\}}\frac{q_iq_j}{4\pi\epsilon_0|\mathbf{r}_j-\mathbf{r}_i|}Erfc\left(\frac{|\mathbf{r}_j-\mathbf{r}_i|}{\sqrt{2}\sigma}\right)\\
&+\frac{V}{2k_xk_yk_z\epsilon_0}\sum_{|\mathbf{k}|>0}\frac{e^{-\frac{\sigma^2 k^2}{2}}}{k^2}|F_{\mathbf{k}}(Q)(m_x,m_y,m_z)|^2\\
&-\frac{1}{4\pi\epsilon_0}\frac{1}{\sqrt{2\pi}\sigma}\sum_{i=0}^{N-1}q_i^2
\end{align*}
\]

以下做一个Python版本的简单测试。

测试思路

为了对比PME算法的收敛性与实空间迭代的收敛性,我们先取一个一维的简单周期性点电荷体系,一方面通过对实空间盒子进行扩张,以得到一个收敛的趋势。另一方面通过PME算法直接截断计算静电势。然后对比两者的结果,按预期来说,PME的结果应该在大量的实空间迭代之后被近似到,也就是
\(V_{PME}=V_{real}(N)\)
。当然,因为不同的插值算法也有可能也会导致计算结果的差异性,所以这里使用了两种插值阶数来进行测试。

代码示例

import numpy as np
from scipy.special import erfc
from tqdm import trange
import matplotlib.pyplot as plt
np.random.seed(4)

num_charges=1000
crd = np.random.random(num_charges) * 10
q = np.random.random(num_charges)
q -= np.sum(q) / q.shape[0]
pbc_box = np.array([10.], np.float64)

def energy(crd, q, pbc_box, levels=0, epsilon=1):
    qij = np.einsum('i,j->ij', q, q)
    if levels == 0:
        dis = np.abs(crd[:, None] - crd[None])
        energy = np.triu(qij / (dis+1e-08) / 4 / np.pi / epsilon, k=1).sum()
    else:
        # left box
        crd1 = crd - levels * pbc_box
        dis = np.abs(crd[:, None] - crd1[None])
        energy = np.triu(qij / (dis+1e-08) / 4 / np.pi / epsilon, k=0).sum()
        # right box
        crd2 = crd + levels * pbc_box
        dis = np.abs(crd[:, None] - crd2[None])
        energy += np.triu(qij / (dis+1e-08) / 4 / np.pi / epsilon, k=0).sum()
    return energy

def pme4(crd, q, pbc_box, epsilon=1):
    sigma = (pbc_box[0] ** 2 / crd.shape[0]) ** (1/6) / np.sqrt(2*np.pi)
    qij = np.einsum('i,j->ij', q, q)
    dis = np.abs(crd[:, None] - crd[None])
    dis = np.where(dis<pbc_box-dis, dis, pbc_box-dis)
    coe = erfc(dis / np.sqrt(2) / sigma)
    real_energy = np.sum(coe * np.triu(qij / (dis+1e-08) / 4 / np.pi / epsilon, k=1))
    self_energy = np.sum(q ** 2) / np.sqrt(2 * np.pi) / sigma / 4 / np.pi / epsilon
    Q = np.zeros(10, dtype=np.float64)
    for idx in range(crd.shape[0]):
        x_floor = np.floor(crd[idx])
        x_shift = np.array([-1.5, -0.5, 0.5, 1.5], np.float32)
        x_idx = (np.floor(x_floor + x_shift) % 10).astype(np.int32)
        x = x_shift
        Q[x_idx[0]] += q[idx]*(-8*x[0]**3+12*x[0]**2+2*x[0]-3)/48
        Q[x_idx[1]] += q[idx]*(8*x[1]**3-4*x[1]**2-18*x[1]+9)/16
        Q[x_idx[2]] += q[idx]*(-8*x[2]**3-4*x[2]**2+18*x[2]+9)/16
        Q[x_idx[3]] += q[idx]*(8*x[3]**3+12*x[3]**2-2*x[3]-3)/48
    Q_ifft = np.fft.ifft(Q)
    sk = np.abs(Q_ifft) ** 2 * Q.shape[0]
    k = np.arange(Q.shape[0]) * 2 * np.pi / Q.shape[0]
    res_energy = np.sum(np.exp(-0.5*sigma**2*k[1:]**2)*sk[1:]/k[1:]**2)/2/epsilon/(2*np.pi/pbc_box[0])
    return real_energy - self_energy + res_energy

def pme6(crd, q, pbc_box, epsilon=1):
    sigma = (pbc_box[0] ** 2 / crd.shape[0]) ** (1/6) / np.sqrt(2*np.pi)
    qij = np.einsum('i,j->ij', q, q)
    dis = np.abs(crd[:, None] - crd[None])
    dis = np.where(dis<pbc_box-dis, dis, pbc_box-dis)
    coe = erfc(dis / np.sqrt(2) / sigma)
    real_energy = np.sum(coe * np.triu(qij / (dis+1e-08) / 4 / np.pi / epsilon, k=1))
    self_energy = np.sum(q ** 2) / np.sqrt(2 * np.pi) / sigma / 4 / np.pi / epsilon
    Q = np.zeros(10, dtype=np.float64)
    for idx in range(crd.shape[0]):
        x_floor = np.floor(crd[idx])
        x_shift = np.array([-2.5, -1.5, -0.5, 0.5, 1.5, 2.5], np.float32)
        x_idx = (np.floor(x_floor + x_shift) % 10).astype(np.int32)
        x = x_shift
        Q[x_idx[0]] += -q[idx]*(x[0]**5-2.5*x[0]**4-1.5*x[0]**3+3.75*x[0]**2+0.3125*x[0]-0.78125)/120
        Q[x_idx[1]] += q[idx]*(x[1]**5-1.5*x[1]**4-6.5*x[1]**3+9.75*x[1]**2+1.5625*x[1]-2.34375)/24
        Q[x_idx[2]] += -q[idx]*(x[2]**5-0.5*x[2]**4-8.5*x[2]**3+4.25*x[2]**2+14.0625*x[2]-7.03125)/12
        Q[x_idx[3]] += q[idx]*(x[2]**5+0.5*x[2]**4-8.5*x[2]**3-4.25*x[2]**2+14.0625*x[2]+7.03125)/12
        Q[x_idx[4]] += -q[idx]*(x[1]**5+1.5*x[1]**4-6.5*x[1]**3-9.75*x[1]**2+1.5625*x[1]+2.34375)/24
        Q[x_idx[5]] += q[idx]*(x[0]**5+2.5*x[0]**4-1.5*x[0]**3-3.75*x[0]**2+0.3125*x[0]+0.78125)/120
    Q_ifft = np.fft.ifft(Q)
    sk = np.abs(Q_ifft) ** 2 * Q.shape[0]
    k = np.arange(Q.shape[0]) * 2 * np.pi / Q.shape[0]
    res_energy = np.sum(np.exp(-0.5*sigma**2*k[1:]**2)*sk[1:]/k[1:]**2)/2/epsilon/(2*np.pi/pbc_box[0])
    return real_energy - self_energy + res_energy

N = 100000
e = np.zeros(N)
for i in trange(N):
    e[i] = energy(crd, q, pbc_box, levels=i)
e = np.cumsum(e)
print (e[0], e[-1])

e2 = pme4(crd, q, pbc_box)
print (e2)

e3 = pme6(crd, q, pbc_box)
print (e3)

x = list(range(N))
plt.figure()
plt.xlabel('Box Layers')
plt.ylabel("Energy")
plt.plot(x, e, label='Normal')
plt.plot(x, np.ones_like(x) * e2, label='PME4')
plt.plot(x, np.ones_like(x) * e3, label='PME6')
plt.legend()
plt.savefig('energy.png')

运行输出为:

-6016.973545020364 -6008.267263384039
-6010.335037181866
-6009.780676419067

这个输出结果表示,不加任何额外的Box时,计算得到的电势能为
-6016
,在左右各加了10万个Box之后,得到的静电势能结果变为
-6008
。而PME计算使用4阶拉格朗日插值时一步就可以得到
-6010
,如果使用6阶的插值,一步就可以得到
-6009
。总体的不同Box Level下的静电势计算结果对比为:

这个结果表示,如果我们使用6阶插值的PME算法,单步的计算就可以得到实空间迭代10000个Box的近似结果。

总结概要

基于前面几篇博客关于PME算法的理论推导,本文给出了一个简单版本的Python代码实现,并且对比了PME算法相比于实空间迭代算法的优越性。从结果上来看,一维的静电势能计算中,PME单步得到的计算结果非常接近于实空间迭代1万个Box的近似结果。

版权声明

本文首发链接为:
https://www.cnblogs.com/dechinphy/p/pme-python.html

作者ID:DechinPhy

更多原著文章:
https://www.cnblogs.com/dechinphy/

请博主喝咖啡:
https://www.cnblogs.com/dechinphy/gallery/image/379634.html

和厂商合作给会员送福利,是我们拓展会员权益的一个方向。

最近我们和算力云服务商
OneThingAI
达成了合作,OneThingAI 给园子的会员提供了不少代金券,让大家免费体验一下 OneThingAI 的算力服务。

OneThingAI 是一家提供AI场景解决方案的算力云服务商,提供了适合AI推理的强大且经济高效的GPU算力服务,比如 NVIDIA RTX 4090 显卡当前包月只需要1095元。

OneThingAI 这次一共提供了1700个权益码,不同级别的会员通过权益码可以兑换对应的代金券,代金券有效期3个月。

博客园年度VIP会员(限1000人)

  • 送50元代金券1张
  • 充值200送100元代金券1次

博客园年度PLUS会员(限300人)

  • 送100元代金券1张
  • 充值200送100元代金券3次

博客园终身VIP会员(限300人)

  • 送50元代金券2张
  • 充值200送100元代金券2次

博客园终身PLUS会员(限100人)

  • 送100元代金券2张
  • 充值200送100元代金券6次

领取权益码链接:
https://vip.cnblogs.com/benefits/one-thing-ai/coupon

权益码兑换方法:通过专属链接
https://onethingai.com/invitationblog?code=WXCP_xcehCwHk
,注册或登录后点击页面右上角的「控制台」链接,然后点击顶部菜单中的「账户中心」,再点击侧边栏的「权益码」,在页面输入框中输入权益码进行绑定,绑定成功即完成兑换。

如果您在领取或者兑换权益码过程中遇到任何问题,可以通过
企业微信
与我们联系。

在使用Navigation时时,你是否遇到了这样一个问题,Navigation加载启动页为入口,在启动页replace到首页,使首页替换换启动页,结果发现不生效,启动页依然存在。

为什么根页面启动页不能直接替换成首页

我们验证后发现当页面是Navigation的子页面时,是可以使用replace替换,当要替换根页面时,却发现不生产,这是因为我们使用NavPathStack只能操作子页面,根页面不在操作范围内,NavPathStack也不能操作根页面。所以不管是用replace,还是先push,再pop启动页,都是不生效的。

在Navigation中如何将启动页替换成首页

直接替换行不通,这里也有其它几种方式,第一种是Navigation根页面设置成首页,启动页跳到首页使用router的方式来处理。这种方式是可行的,但总觉得不是那么的完美,我都用Navigation了,为什么还要用router呢,官方一再强调不推荐使用router,推荐使用navigation。所以我们还有其它方式吗?

直接用Navigation替换不行,那我们自己手动来替换总行吧,在Navigation根页面加一个标签,首先加载启动页,然后更改标签的值,再去显示首页。我们以V2状态管理为例

@Entry
@ComponentV2
struct Index {
  @Local isShowHome: boolean = false

  build() {
    Navigation() {
      if (this.isShowHome) {
        HomePage()
      } else {
        SplashPage({
          onChangePage: () => {
            this.isShowHome = true
          }
        })
      }
    }
  }
}

@ComponentV2
export struct SplashPage {
  @Event onChangePage: VoidCallback = () => undefined

  aboutToAppear(): void {
    setTimeout(() => {
        this.onChangePage()
      }, 1000)
  }

  build() {
    Stack().width('100%').height('100%')
  }
}

@ComponentV2
export struct HomePage {

  build() {
    Stack().width('100%').height('100%')
  }
}

我们在启动页中使用@Event定义一个onChangePage事件,延时1秒后通知Navigation将根页面切换成首页。

这种方式虽然也可以,但总觉得还要手动通过if来切换页面,不是那么的优雅。为什么不能统一用NavPathStack来切换页面呢?不知道Navigation这么设计的原因是什么,不过大家想把启动页和首页也放在Navigation中统一管理,可以使用HMRouter这个三方库来处理,HMRouter是对Navigation做了封装,完成兼容Navigation。这几种方式都不太完美,要是需要单独特殊处理,要么需要引入三方库,大家结合自己的实际情况来选择吧。

最近给一个私募大佬帮忙做了一些股票交易有关的系统,其中涉及到行情数据抓取的问题,一番摸索之后,把成果在这里做个分享。

我把行情抓取的部分,和一个写手记的小功能,单独拿了出来放在一个小系统里面,可以免费使用:
https://rich.shengxunwei.com/

先简单介绍下这个小系统的样子,然后我会详细的解释如何高性能实时抓取股票行情。

可以添加自己关注股票列表,支持股票、场内基金、可转债:

股票详情数据:

场内基金详情数据:

可转债详情数据:

在线演示:
https://rich.shengxunwei.com/


行情抓取的数据来源

目前主要的数据来源是几个财经网站,比如东财:

实时行情抓取方法

实时行情抓取的一个核心类是 HttpClient 类,然后只需访问东财的网站即可。

创建 HttpClient

下面的大多数示例都重复使用同一 HttpClient 实例,因此只需配置一次。 要创建 HttpClient,请使用 HttpClient 类构造函数。

// HttpClient lifecycle management best practices:
// https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use
private static HttpClient sharedClient = new()
{
    BaseAddress = new Uri("https://jsonplaceholder.typicode.com"),
};

前面的代码:

实例化新的 HttpClient 实例作为 static 变量。 根据准则,建议在应用程序的生命周期内重复使用 HttpClient 实例。
将 HttpClient.BaseAddress 设置为 "
https://jsonplaceholder.typicode.com
"。
发出后续请求时,此 HttpClient 实例将使用基址。 若要应用其他配置,请考虑:

设置 HttpClient.DefaultRequestHeaders。
应用非默认 HttpClient.Timeout。
指定 HttpClient.DefaultRequestVersion。

虽然面向 Android 设备(如 .NET MAUI 开发),但必须将 android:usesCleartextTraffic="true" 添加到 AndroidManifest.xml 中的

。 这将启用明文流量,例如 HTTP 请求;否则由于 Android 安全策略,默认情况下会禁用。 请考虑以下示例 XML 设置:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application android:usesCleartextTraffic="true"></application>
  <!-- omitted for brevity -->
</manifest>

HTTP 内容

HttpContent 类型用于表示 HTTP 实体正文和相应的内容标头。 对于需要正文的 HTTP 方法(或请求方法)POST、PUT 和 PATCH,可使用 HttpContent 类来指定请求的正文。 大多数示例演示如何使用 JSON 有效负载准备 StringContent 子类,但还有针对其他内容 (MIME) 类型的其他子类。

  • ByteArrayContent:提供基于字节数组的 HTTP 内容。
  • FormUrlEncodedContent:为使用 "application/x-www-form-urlencoded" MIME 类型编码的名称/值元组提供 HTTP 内容。
  • JsonContent:提供基于 JSON 的 HTTP 内容。
  • MultipartContent:提供使用 "multipart/*" MIME 类型规范进行序列化的 HttpContent 对象的集合。
  • MultipartFormDataContent:为使用 "multipart/form-data" MIME 类型进行编码的内容提供容器。
  • ReadOnlyMemoryContent:提供基于 ReadOnlyMemory
    的 HTTP 内容。
  • StreamContent:提供基于流的 HTTP 内容。
  • StringContent:提供基于字符串的 HTTP 内容。
  • HttpContent 类还用于表示 HttpResponseMessage 的响应正文,可通过 HttpResponseMessage.Content 属性访问。

HTTP Get

GET 请求不应发送正文,而是用于(如方法名称所示)从资源检索(或获取)数据。 要在给定 HttpClient 和 URI 的情况下发出 HTTP GET 请求,请使用 HttpClient.GetAsync 方法:

static async Task GetAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.GetAsync("todos/3");
    
    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();
    
    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output:
    //   GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 3,
    //     "title": "fugiat veniam minus",
    //     "completed": false
    //   }
}

HTTP Get from JSON

https://jsonplaceholder.typicode.com/todos
终结点返回“todo”对象的 JSON 数组。 这些对象的 JSON 结构如下所示

[
  {
    "userId": 1,
    "id": 1,
    "title": "example title",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "another example title",
    "completed": true
  },
]

本地计算机或应用程序配置文件可以指定使用默认代理。 如果指定了 Proxy 属性,则 Proxy 属性中的代理设置会覆盖本地计算机或应用程序配置文件,并且处理程序将使用指定的代理设置。 如果未在配置文件中指定代理,并且未指定 Proxy 属性,则处理程序将使用从本地计算机继承的代理设置。 如果没有代理设置,则请求将直接发送到服务器。