在SignalR Hub中提供下载进度

时间:2014-05-17 22:01:17

标签: asp.net signalr

我有一个ASP.Net网站,我从一个远程站点下载一个大的zip文件到服务器。此文件已传输到客户端,但仍保留在服务器上。我想使用SignalR向用户提供进度更新。当我使用下面的代码时:

public class InstallController : Hub
{
    public void Send( string message )
    {
        Clients.All.AddMessage( message );
    }

    public void FileDownload()
    {
        WebClient client = new WebClient();
        client.DownloadProgressChanged += new DownloadProgressChangedEventHandler( client_DownloadProgressChanged );
        client.DownloadFileCompleted += new AsyncCompletedEventHandler( client_DownloadFileCompleted );
        client.DownloadFileAsync( new Uri( "http://someserver.com/install/file.zip" ), @"\file.zip" );

    }

    /* callbacks for download */
    void client_DownloadProgressChanged( object sender, DownloadProgressChangedEventArgs e )
    {
        double bytesIn = double.Parse( e.BytesReceived.ToString() );
        double totalBytes = double.Parse( e.TotalBytesToReceive.ToString() );
        double percentage = bytesIn / totalBytes * 100;

        this.Send( String.Format( "Download progress: {0}%", percentage.ToString() ) );
    }

    void client_DownloadFileCompleted( object sender, AsyncCompletedEventArgs e )
    {
        this.Send( "Finished downloading file..." );
    }     
}

我得到例外:

  

类型' System.InvalidOperationException'的例外情况发生在   System.Web.dll但未在用户代码中处理

     

附加信息:无法启动异步操作   这次。异步操作只能在一个内部启动   异步处理程序或模块或在页面中的某些事件期间   生命周期。如果在执行页面时发生此异常,请确保   页面标记为<%@ Page Async =" true" %取代。这个例外可能   还表示试图调用" async void"方法,即   通常在ASP.NET请求处理中不受支持。相反,   异步方法应该返回一个Task,并且调用者应该等待   它

我已经看过几次提及使用HttpClient而不是WebClient,但我不知道如何从中获取进展。

1 个答案:

答案 0 :(得分:2)

“这是所有关于SynchronizationContext”
http://msdn.microsoft.com/en-us/magazine/gg598924.aspx
自从在.NET中添加新技术和新功能以来,这句话变得非常普遍。

简要..有几个组件,例如BackgroundWorkerWebClient,它们将SynchronizationContext隐藏到捕获和使用中,这意味着您需要尊重生命周期请求,ASP.NET组件的生命周期。

具体而言,HTTP方法(GET和POST)始终以相同的方式工作,客户端向服务器提交HTTP请求,然后服务器向客户端返回响应,应用程序将尝试确保发生这种情况,ASP.NET的SynchronizationContext就是为此而设计的。

更多信息:

即使使用SignalR的请求也包含相同的ASP.NET SynchronizationContext,因此您需要在当前SynchronizationContext“之外”工作或以正确的方式使用它。

SignalR旨在使用异步编程,默认情况下使用TPL,您可以利用它,检查http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server#asyncmethodshttp://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server#asyncclient

您可以通过多种方式解决问题。
如果你想使用SignalR来显示进度..我会做类似下面的代码(我仍在使用.NET 4.0,使用带有TaskAsync方法的.NET 4.5更容易)。

public Task<string> FileDownload()
{
    var client = new WebClient();
    client.DownloadProgressChanged += (sender, args) => client_DownloadProgressChanged(sender, args, this.Context.ConnectionId);
    client.DownloadFileAsync(new Uri("https://epub-samples.googlecode.com/files/cc-shared-culture-20120130.epub"), @"C:\temp\file.zip");

    var result = new TaskCompletionSource<string>();
    AsyncCompletedEventHandler clientOnDownloadFileCompleted = (sender, args) =>
    {
        client.Dispose();
        if (args.Error != null)
        {
            result.SetException(args.Error); //or result.SetResult(args.Error.Message);
            return;
        }
        result.SetResult("Downloaded");
    };

    client.DownloadFileCompleted += clientOnDownloadFileCompleted;
    return result.Task;
}

private static void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e,
                                                   string connectionId)
{
    GlobalHost.ConnectionManager.GetHubContext<SomeHub>()
              .Clients.Client(connectionId)
              .NotifyProgress(e.ProgressPercentage);
}

请记住,这只是一个示例,您可以改进它们处理断开连接和取消的方式,以及可能发生的其他事情(取决于您的应用程序逻辑)。

也可以使用“解决方法”(不推荐):

代码与上面的内容非常相似。