2

I've been pulling my hair out for weeks now and I need some help. This is a SharePoint utility that copies RDL files to project sites. Why on earth are my IAsyncResult.EndInvokes called after processing the entire list? On average, I can copy all RDL files to 1 project site in 10 seconds. With this in mind, I would expect to hit my breakpoint inside the IAsyncResult method within a minute. Instead, the breakpoint doesn't hit until after all reports have been copied to all projects, which then the breakpoint hits in a rapid fire succession. What am I doing wrong here?

I'm constrained to using .NET 3.5 so TAP processing is not an option :(

Delegate:

public delegate string CopyReportsParallel(string targetSiteUrl, List<SourceReportInfo> reports, EditorSettings settings);

This method compiles the list of RDL's and target project sites. For each target site, a delegate it created to handle the copy logic:

private static void bw_DoWork(Object sender, DoWorkEventArgs e)
    {            
        int workerIndex = bwObject.targetProjectSiteUrls.Count;
        _totalCount = bwObject.targetProjectSiteUrls.Count;
    foreach (string s in bwObject.targetProjectSiteUrls)
    {
        if (cancelled)
            break;

        //Get a delegate for the operation
        CopyReportsParallel d = new CopyReportsParallel(ParallelCopyLogic);
        IAsyncResult ar = d.BeginInvoke(s, bwObject.reports, bwObject.settings, BackgroundWorkerIsDone, d);               
    }

    waitHandle.WaitOne();

}

Here is my call back method. Ideally I would want as many threads to process asynchronously and report back immediately after finishing so that I could update the webpage with a progress bar.

    private static void BackgroundWorkerIsDone(IAsyncResult ar)
    {
        CopyReportsParallel d = (CopyReportsParallel)ar.AsyncState;

        string result = d.EndInvoke(ar);

        lock ((_lockObject))
        {
            _completedCount += 1;

            if (logTheHistory)
            {
                int completedPercent = _completedCount * 100 / _totalCount;
                string report = result + Environment.NewLine + "Update - " + completedPercent.ToString() + "% Complete " + " # of Projects Completed: " + _completedCount.ToString();

                bw.ReportProgress(completedPercent, report);
            }

            if (_completedCount == _totalCount)
            {
                waitHandle.Set();
            }    
        }
    }

The parallel copy logic (per request):

private static string ParallelCopyLogic(string targetSiteUrl, List<SourceReportInfo> reports, EditorSettings settings)
        {
            string _runDetail = "";
            _runDetail += (System.DateTime.Now.ToString() + " - Working on Project: " + targetSiteUrl + Environment.NewLine);

            using (_reportSvc = new ReportingService2010())
            {
                _reportSvc.Url = reportSvcURl;
                _reportSvc.Credentials = System.Net.CredentialCache.DefaultCredentials;
                _reportSvc.PreAuthenticate = true;
                _reportSvc.Timeout = Timeout.Infinite;

                using (SPSite targetSite = new SPSite(targetSiteUrl))
                using (SPWeb targetWeb = targetSite.OpenWeb())
                {
                    foreach (SourceReportInfo reportInfo in reports)
                    {
                        try
                        {
                            SPDocumentLibrary targetReportLibrary = targetWeb.Lists[reportInfo.SourceReport.ParentList.RootFolder.Name] as SPDocumentLibrary;
                            SPFile targetReportFile = targetReportLibrary.RootFolder.Files.Add(reportInfo.SoureReportUrl, reportInfo.SourceReportFileBytes, reportInfo.SourceReport.Properties, true);
                            SPListItem targetReport = targetReportFile.GetListItem();

                            string sourceURL = reportInfo.SourceReport.Web.Url + "/" + reportInfo.SourceReport.Url;
                            string destinationURL = targetReport.Web.Url + "/" + targetReport.Url;
                            string targetListUrl = targetReport.Web.Url + "/" + targetReport.ParentList.RootFolder.Name;

                            bool dsUpdated = false;
                            bool paramUpdated = false;
                            bool ddUpdated = false;

                            if (!targetReport.HasUniqueRoleAssignments)
                                targetReport.BreakRoleInheritance(true);

                            foreach (SPRoleAssignment role in reportInfo.RoleAssignments)
                                targetReport.RoleAssignments.Add(role);

                            targetReport.Update();


                            // Update copied Items
                            if (reportInfo.SoureReportUrl.EndsWith(".rdl"))
                            {
                                SPSecurity.RunWithElevatedPrivileges(new SPSecurity.CodeToRunElevated(delegate()
                                {
                                    ReportService.DataSource[] sourceDataSources = _reportSvc.GetItemDataSources(sourceURL);
                                    ReportService.DataSource[] destDataSources = _reportSvc.GetItemDataSources(destinationURL);

                                    for (int i = 0; i < sourceDataSources.Length; i++)
                                    {
                                        DataSourceReference sourceDs = sourceDataSources[i].Item as DataSourceReference;
                                        DataSourceDefinition sourceDef = sourceDataSources[i].Item as DataSourceDefinition;
                                        DataSourceDefinition destDef = destDataSources[i].Item as DataSourceDefinition;


                                        // Updates the target data source references
                                        if (sourceDs != null && sourceDs.Reference != null && sourceDataSources[i].Name == destDataSources[i].Name)
                                        {
                                            string sourceDsName = System.IO.Path.GetFileName(sourceDs.Reference);

                                            destDataSources[i].Item = new DataSourceReference { Reference = targetListUrl + "/" + sourceDsName };
                                            dsUpdated = true;
                                        }

                                        //Updates the target data source definitions
                                        else if (sourceDef != null && destDef != null && sourceDataSources[i].Name == destDataSources[i].Name)
                                        {
                                            //Set data source credentials
                                            destDef.Enabled = true;
                                            destDef.CredentialRetrieval = sourceDef.CredentialRetrieval;
                                            destDef.ImpersonateUser = sourceDef.ImpersonateUser;
                                            destDef.WindowsCredentials = sourceDef.WindowsCredentials;

                                            if (settings != null && destDef.CredentialRetrieval == CredentialRetrievalEnum.Store)
                                            {
                                                destDef.UserName = settings.DataAccessUserName;
                                                settings.GetDataAccessPassword(destDef);
                                            }

                                            destDataSources[i].Item = destDef;
                                            ddUpdated = true;
                                        }
                                    }

                                    if (dsUpdated | ddUpdated)
                                    {
                                        _reportSvc.SetItemDataSources(destinationURL, destDataSources);
                                        dsUpdated = false;
                                        ddUpdated = false;
                                    }

                                    //Updates the target report's project code default parameter
                                    ItemParameter[] parameters = _reportSvc.GetItemParameters(destinationURL, null, false, null, null);

                                    foreach (ItemParameter item in parameters)
                                    {
                                        //Have to check if prompt text is empty as this API doesnt expose hidden property
                                        if (item.Name.ToLower() == "projectcode" && string.IsNullOrEmpty(item.Prompt) && item.DefaultValues.Length == 1)
                                        {
                                            item.DefaultValues[0] = targetWeb.Name;
                                            paramUpdated = true;

                                            break;
                                        }
                                    }

                                    if (paramUpdated)
                                    {
                                        _reportSvc.SetItemParameters(destinationURL, parameters);
                                        paramUpdated = false;
                                    }
                                }));
                            }
                            else if (reportInfo.SoureReportUrl.EndsWith(".rsds"))
                            {
                                //Update Shared Datasets
                                DataSourceDefinition sourceDef = _reportSvc.GetDataSourceContents(sourceURL);
                                DataSourceDefinition destinationDef = _reportSvc.GetDataSourceContents(destinationURL);

                                destinationDef.Enabled = true;
                                destinationDef.CredentialRetrieval = sourceDef.CredentialRetrieval;
                                destinationDef.ImpersonateUser = sourceDef.ImpersonateUser;
                                destinationDef.WindowsCredentials = sourceDef.WindowsCredentials;

                                if (settings != null && destinationDef.CredentialRetrieval != CredentialRetrievalEnum.Integrated)
                                {
                                    destinationDef.UserName = settings.DataAccessUserName;
                                    settings.GetDataAccessPassword(destinationDef);
                                }

                                _reportSvc.SetDataSourceContents(destinationURL, destinationDef);
                            }

                            _runDetail += (System.DateTime.Now.ToString() + " - Finished updating report:  " + targetReport.Name.ToString() + Environment.NewLine);


                        }
                        catch (Exception ex)
                        {
                            _runDetail += (System.DateTime.Now.ToString() + " - There was an error on project: " + targetSiteUrl + Environment.NewLine);
                            throw new InvalidOperationException(string.Format("Error while updating report {0} on Project Site {1}. Exception:{2}", reportInfo.SoureReportUrl, targetSiteUrl, ex.Message), ex);

                        }
                    }
                }
            }
            return _runDetail;
        }

Please let me know what you think or if you need any more info. I've looked and looked and looked so I'm not trying to have anyone "do my homework here". I would love to learn a thing or two from some more seasoned programmers.

Thanks!

Nick H.
  • 1,616
  • 14
  • 20
  • 1
    Can you show the body of `ParallelCopyLogic`? the problem may be in there. – Scott Chamberlain Feb 26 '14 at 22:25
  • It's a lot of code to post... Would you be looking for something specific? The copy logic is 100% self contained, as in it doesn't call any other methods. It runs through the logic in about 10 seconds and then returns a string containing the run details. – Nick H. Feb 26 '14 at 22:29
  • 10 seconds is alot for CPU-bound operation, do you perform any IO (e.g reading from a database) per chance? – alexm Feb 26 '14 at 22:31
  • Specifically anything that would cause a blocking call, perhaps a `lock` or maybe a IO call to a network resource. The problem is not in the code you have posted (or at least I don't see anything that would cause it), so the only remaining thing that could cause it must reside in `ParallelCopyLogic` – Scott Chamberlain Feb 26 '14 at 22:33
  • Before I updated the utility, it took just over a minute per project! Most of it is IO.. It takes report definitions from a template site and copies them out to the selected project sites. In this we're using a reporting web service and of course the SharePoint content database. – Nick H. Feb 26 '14 at 22:33
  • I just posted the copy logic. Let me know what you guys think.. I'm at whits end. – Nick H. Feb 26 '14 at 22:35
  • PS.. Did I mention I'm a SQL developer and not a C# programmer? :/ don't feel bad for looking at this at saying "wtf??" Thanks for the help everyone! – Nick H. Feb 26 '14 at 22:36
  • If it is a single connection it is possible all requests are handled sequentially negating all benefits of the parallel execution. – alexm Feb 26 '14 at 22:36
  • 2
    I don't see anything obviously causing it in `ParallelCopyLogic` either, sorry. If this was my project my next step would be load up a [code profiler](http://stackoverflow.com/questions/3927/what-are-some-good-net-profilers) and see if I can track down which line all of the invocations are taking the most time on and see if there is something special about that line that is causing this issue. – Scott Chamberlain Feb 26 '14 at 22:42
  • I absolutely will, thanks Scott... Upvote for being friendly. – Nick H. Feb 26 '14 at 22:44

1 Answers1

1

There is nothing wrong with this logic. The compiler will fire off ASYNC delegates while at the same time receive their IASYNC callbacks.