ASP.NET 在 Web Farm 环境中的多线程
ASP.NET 在 Web Farm 环境中的多线程
在我最近的一个项目中,我遇到了一种情况,需要根据用户输入在服务器上生成两个文件。这两个文件都可能很大(就移动设备而言),并且服务器生成和用户下载都需要很长时间。这两个文件必须按顺序生成,因为第二个文件依赖于第一个文件。实现此目的的一种方法是生成这两个文件,然后在一个请求-响应中将下载 URL 返回给用户。这种方法的问题在于,用户必须等待很长时间才能让服务器完成这项工作。一个明显的改进是在生成第一个文件后立即返回,同时生成一个新线程来生成第二个文件。这样,在请求响应后,用户可以开始下载第一个文件,而服务器正在生成第二个文件。这种方法的一个问题是,用户不知道第二个文件何时准备好下载。我的解决方案是创建另一个页面,允许用户检查第二个文件的状态。如果准备就绪,响应将包含下载 URL,以便用户可以开始下载。如果尚未准备好,用户可以等待或做其他事情,稍后检查状态。这在我的情况下运行良好,因为下载第一个文件的时间几乎总是比生成第二个文件所需的时间长。
这种方法的另一个问题是我的 ASP.NET 应用程序运行在 Web Farm 环境中,我有一个共享文件存储,用于存储生成的文件,而新生成的线程没有正确的权限来写入共享文件存储。下图显示了我的一个由两个 Web 服务器 WS1 和 WS2 组成的简单 Web Farm,以及文件存储。文件存储只是两个 Web 服务器之一上的共享文件夹。
我使用以下步骤配置 Web 服务器,以便它们可以访问共享文件夹。这些步骤适用于 Windows 2000(高级)服务器操作系统。是的!我仍然在使用 Windows 2000 服务器。 :)
- 在 WS1 和 WS2 上创建一个 Windows 用户帐户,例如 "
SharedUsr
"。 - 在两个服务器上的 web.config 中添加
<identity impersonate="true" userName="SharedUsr" password="xxxxxx"/>
。 - 在两个服务器上运行这些命令,授予 "
SharedUsr
" 访问我的 ASP.NET 应用程序使用的共享文件夹的权限
C:\WINNT\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -ga WS1\SharedUsr
C:\WINNT\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -ga WS2\SharedUsr - 确保 "
SharedUsr
" 具有共享文件夹的写入权限
但是,如果当前线程生成一个新线程来写入共享文件夹,则此方法不起作用,因为新线程不会自动模拟 "SharedUsr
"。我必须编写代码来强制模拟。以下是代码。为了清晰起见,我省略了一些细节。
......
using System.IO;
using System.Security.Principal;
using System.Threading;
public partial class GenerateFiles : System.Web.UI.Page
{
private WindowsIdentity wid = null;
protected void Page_Load(object sender, EventArgs e)
{
//Generate file1 ......
wid = WindowsIdentity.GetCurrent(); //This is "SharedUsr"
try
{
//Use a thread from the threadpool to generate file2
if (ThreadPool.QueueUserWorkItem(new WaitCallback(File2Callback),
new File2Generator(file2Location)))
{
//Return file1 URL to user so he can start to download file1
Response.Write(file1URL);
}
else
{
Response.Write("Server too busy. The work item could not be queued.");
}
}
catch (Exception ex)
{
Response.Write(ex.Message);
}
}
private void File2Callback(object o)
{
//Set the pooled worker thread's principal to that of wid
WindowsPrincipal principal = new WindowsPrincipal(wid);
Thread.CurrentPrincipal = principal;
//Make the pooled worker thread to impersonate 'SharedUsr'
WindowsImpersonationContext wic = wid.Impersonate();
//Generate file2
File2Generator f2gen = (File2Generator)o;
f2gen.Generate();
//Undo impersonation before return to threadpool
wic.Undo();
}
}
在 Page_Load
的开头,我们假设 file1
已经生成。然后,我们使用线程池中的工作线程来排队生成 file2
以供执行。QueueUserWorkItem
方法有两个参数。第一个参数指定当工作线程轮到时要调用的函数。第二个参数是要传递给函数的数据。在我们的例子中,它是一个 File2Generator
类的实例,它包含生成 file2
所需的数据和功能。这里没有列出 File2Generator
的实现,因为细节对于说明问题并不重要。
回调函数 File2Callback
将在工作线程上执行,该工作线程的 Windows 身份为 "ASPNET
",而不是 "SharedUsr
"。因此,在其中,我们需要在调用 File2Generator
的 Generate
方法之前更改工作线程的模拟。