65.9K
CodeProject 正在变化。 阅读更多。
Home

我如何优化 Silverlight 异步 Web 服务消耗

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (15投票s)

2010 年 5 月 26 日

CPOL

4分钟阅读

viewsIcon

45113

通过一些前期的设计思考,我在当前项目中实现了一些显著的代码优化。

引言

当你从事一个项目时,你很少有时间(有时甚至没有意愿)回过头去重构代码,使其变得 a) 性能更好,或 b) 更易于维护。此外,你没有时间对你的更改进行回归测试,所以通常(也是最推荐的)做法是“保持现状”。我很幸运能够进行这类优化,因为我的日程安排相对灵活(我是“先驱者”),并且我正在等待 IT 人员解决一个服务器问题。

Notice

本文展示了我在代码中实现的内容,仅应作为实现类似结果的示例,并且你应该考虑以下几点

  • 由于无法创建必要的基础设施(数据库、Web 服务、.etc,等等),无法为给定技术提供示例应用程序,因此没有可下载的示例代码。
  • 由于这不会影响 UI 或任何视觉指示代码正在执行某些操作,因此没有屏幕截图。
  • 我假设你熟悉代码中使用的所有术语和基础技术。如果你是初学者,请找人(不一定是我)向你解释其中的细节。
  • 最后,这段文字太长了,不能算是一个小技巧,所以我写成了一篇文章。

当前状况

我正在开发一个使用 Web 服务的 Silverlight 应用程序,并根据检索到的数据创建一个多面板图表页面,每个图表需要执行至少两次,(到目前为止)最多四次查询。Silverlight 中 Web 服务的异步性质迫使你处理每次访问 Web 服务的“已完成”事件。在我们的特定案例中,这意味着我们至少需要为一些图表提供五个方法,以便我们能够按正确的顺序级联查询。本质上,它看起来是这样的,并且是每个图表类所必需的

private void RetrieveData()
{
    // add event handler for web service method step1_completed event
    // build parameter string (if necessary)
    // call web service method
}

private void step1_Completed(sender, args)
{
    // process returned dataset
    // handle error condition if necessary
    // remove step1_completed event handler
    // add event handler for web service method step2_completed event
    // build parameter string (if necessary)
    // call web service method
}

private void step2_Completed(sender, args)
{
    // process returned dataset
    // handle error condition if necessary
    // remove step2_completed event handler
    // add event handler for web service method step3_completed event
    // build parameter string (if necessary)
    // call web service method
}

private void step3_Completed(sender, args)
{
    // process returned dataset
    // handle error condition if necessary
    // remove step3_completed event handler
    // add event handler for web service method step4_completed event
    // build parameter string (if necessary)
    // call web service method
}

private void step4_Completed(sender, args)
{
    // process returned dataset
    // handle error condition if necessary
    // remove step4_completed event handler
    // build chart with retrieved data
}

当然,代码会更庞大一些,但我只包含了处理四次查询图表所需的主要步骤。我觉得我不仅可以抽象出 Web 服务访问代码,还可以减少执行最繁琐查询需求所需的代码量,以下是我的做法。

抽象 - 优化装罐

首先,我利用了 Web 服务本身的架构。对于图表,我们有一个单一的方法,能够调用任何存储过程,并接受你可能想到的任意数量和类型的参数,因为该 Web 服务方法接受要执行的存储过程的名称,以及一个表示存储过程所需参数的 XML 字符串(你可以在我的另一篇技巧中找到详细信息(传递动态参数列表到 Web 服务[^])。Web 服务这一方面是此技术可行的主要原因。

首先,我创建了一个类来包含我的查询参数(存储过程名称和 xml 参数字符串)。如你所见,它只是我们提供给 Web 服务所需内容的容器。

public class QueryItem
{
    public string StoredProc { get; set; }
    public string XmlParams { get; set; }
    public QueryItem(string storedProc, string xmlParams)
    {
        StoredProc = storedProc;
        XmlParams  = xmlParams;
    }
}

然后,我创建了一个基类,用于派生我的图表对象。以下代码片段中的注释说明了各种数据成员的用途

public class DataRetrieval
{
    // holds the results of each query (returned by the web service as XML data strings)
    protected List<string>    m_queryResults     = new List<string>();

    // holds the list of query items we're going to hit the web service with (can be 1 or more)
    protected List<QueryItem> m_queryItems       = new List<QueryItem>();

    // tells us when we can stop processing queries
    private   int             m_retrievalCount   = 0;

    // allows the programmer to specify whether to stop querying if an error 
    // condition was triggered in the query
    protected bool            m_stopOnError      = false;

    // inidcates that an error was triggered/detected
    protected bool            m_errorEncountered = false;

    // event handler delegate - for when our query set has been completed

    public delegate void RetrievalCompleteHandler (object sender, MetricEventArgs ca); 

    // event declaration 

    public event RetrievalCompleteHandler RetrievalComplete = delegate{}; 

    //--------------------------------------------------------------------------------
    public DataRetrieval()
    {
    }

我需要一种方法来实际启动检索过程。以下方法初始化控件变量,为 Web 服务方法添加已完成事件处理程序,然后触发对该方法的第一次调用。

    //--------------------------------------------------------------------------------
    public void Retrieve(bool stopOnError)
    {
        m_retrievalCount   = 0;
        m_stopOnError      = stopOnError;
        m_errorEncountered = false;

        if (m_queryItems.Count == 0)
        {
            throw new Exception("No query items specified. Data retrieval process terminated.");
        }

        Globals.service.GetMetricDataCompleted += new EventHandler<SvcReference.GetMetricDataCompletedEventArgs>
                                                                  (service_GetMetricDataCompleted);

        Globals.service.GetMetricDataAsync(m_queryItems[m_retrievalCount].StoredProc, 
                                           m_queryItems[m_retrievalCount].XmlParams);

    }

接下来,我需要在已完成事件处理程序中添加代码,以检查错误,存储最后一个查询的结果,并在必要时自动启动下一个查询,或发布检索完成事件(以合适的为准)。

    //--------------------------------------------------------------------------------
    private void service_GetMetricDataCompleted(object sender, SvcReference.GetMetricDataCompletedEventArgs e)
    {
        bool ok = DataIsOK(e.Result);
        bool stop = (this.m_stopOnError && !ok);
        m_queryResults.Add(e.Result);

        if (m_retrievalCount < m_queryItems.Count - 1 && !stop)
        {
            m_retrievalCount++;
            Globals.service.GetMetricDataAsync(m_queryItems[m_retrievalCount].StoredProc, 
                                               m_queryItems[m_retrievalCount].XmlParams);
        }
        else
        {
            Globals.service.GetMetricDataCompleted -= new EventHandler<SvcReference.GetMetricDataCompletedEventArgs>
                                                                      (service_GetMetricDataCompleted);
            RetrievalComplete(this, new MetricEventArgs());
        }
    }

由于派生图表对象的代码很可能由我以外的人编写,他们可能对如何指示存储过程中的错误有不同的想法。我的做法是返回一个以“Exception”开头的字符串。这意味着空字符串或以“<”字符开头的字符串都是有效字符串,这就是默认的错误检测行为,但程序员可以自由地想出自己的方法。

    //--------------------------------------------------------------------------------
    protected virtual bool DataIsOK(string queryResult)
    {
        bool isOK = (queryResult.StartsWith("<") || queryResult == "");
	m_errorEncounter = !isOK;
        return isOK;
    }

最后,有一个简单的方法可以帮助程序员创建他的查询项。

    //--------------------------------------------------------------------------------
    protected QueryItem MakeQueryItem(string procName, string xmlData)
    {
        QueryItem item = new QueryItem(procName, xmlData);
        return item;
    }
}

用法

在实现了新的基类后,我能够删除超过 400 行不必要的代码。这是在派生类中实现的(通用)代码。在第一个方法中,我们创建查询项,添加已完成事件的处理程序,并调用 DataRetrieval.Retrieve() 方法

//--------------------------------------------------------------------------------
public void StartRetrieval()
{
    //
    this.m_queryItems.Add(MakeQueryItem(storedProcName1, 
                                        new XElement("Parameters", 
                                                     Globals.MakeXmlParameter("@param1", 0),
                                                     Globals.MakeXmlParameter("@bparam2", "param")).ToString()));
    this.m_queryItems.Add(MakeQueryItem(storedProcName2, 
                                        new XElement("Parameters", 
                                                     Globals.MakeXmlParameter("@param1", 1),
                                                     Globals.MakeXmlParameter("@param2", "param")).ToString()));
    this.m_queryItems.Add(MakeQueryItem(storedProcName3, 
                                        new XElement("Parameters", 
                                                     Globals.MakeXmlParameter("@param1", 2),
                                                     Globals.MakeXmlParameter("@param2", "param2"),
                                                     Globals.MakeXmlParameter("@param3", "param3")).ToString()));
    this.m_queryItems.Add(MakeQueryItem(storedProcName4, 
                                        new XElement("Parameters", 
                                        Globals.MakeXmlParameter("@param1", 3),
                                        Globals.MakeXmlParameter("@param2", "test"),
                                        Globals.MakeXmlParameter("@param3", "test2")).ToString()));
    this.RetrievalComplete += new RetrievalCompleteHandler(dataRetrieval_RetrievalComplete);
    Retrieve(true);
}

最后,我们有了已完成事件处理程序,一旦数据检索完毕,它就会执行所有必要的操作。

//--------------------------------------------------------------------------------
void DelinquentBaseData_RetrievalComplete(object sender, MetricEventArgs ca)
{
    ProcessMainData    (this.m_queryResults[0]);
    ProcessBarchartData(this.m_queryResults[1]);
    ProcessGeneralData (this.m_queryResults[2]);
    this.dataMember   = this.m_queryResults[3];

    // Since this is in a data retrieval class, we have to signal that we have 
    // retrieved and processed our data, and the parent chart object can now populate 
    // the chart panels.
    RaiseDataRetrievalCompletedEvent(new MetricEventArgs());
}

结论

请记住,这是我对代码进行的优化,并且仅因为所使用的 Web 服务的设计而可行。请参阅本文前面引用的技巧。

历史

  • 2010 年 5 月 26 日 - 原始版本
© . All rights reserved.