Using Background Worker and process.start not able to retrieve progress status - backgroundworker

Good evening,
I just started playing around with C# and I tried creating a GUI for a program that runs in command line. I have been able to get it running, but now I am stuck trying to implement a progress bar to it.
I have read other post but I am unable to find the exact issue or to understand how to apply the solution to my issue.
Here is my code (apologize if this is very messy):
private void MethodToProcess(Object sender, DoWorkEventArgs args)
{
// Set all the strings for passthrough
String USMTPath_Work = USMTPath + USMTArch;
String USMTPath_full = USMTPath_Work + #"\Scanstate.exe";
String USMTFlags_Capture = #"/c /v:13 /o /l:scanstate.log /localonly /efs:copyraw";
String Argument_full = SavePath + XML1 + XML2 + USMTFlags_Capture;
// Test that USMT path is correct
if (USMTPath == null)
{
MessageBox.Show("Error: There is no USMT Path defined.");
return;
}
// Test that Windows folder is correct when offline
/* if (Windows_Path == null)
{
MessageBox.Show("Error: There is no Windows Path to capture.");
return;
} */
// Runs the capture
System.Diagnostics.Process Scanstate = new System.Diagnostics.Process();
Scanstate.StartInfo.FileName = USMTPath_full;
Scanstate.StartInfo.Arguments = Argument_full;
Scanstate.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
Scanstate.StartInfo.WorkingDirectory = USMTPath_Work;
//Scanstate.StartInfo.UseShellExecute = false;
Scanstate.StartInfo.CreateNoWindow = true;
//Scanstate.StartInfo.RedirectStandardOutput = true;
Scanstate.Start();
Scanstate.WaitForExit();
String Str_ExitCode = Scanstate.ExitCode.ToString();
if (Scanstate.ExitCode == 1)
MessageBox.Show("Error: Data has not been captured. Please check the log files for details.");
if (Scanstate.ExitCode == 0)
MessageBox.Show("Success: Data has been captured. For more information, check log files.");
else
{
MessageBox.Show("Error: Unknown error has occurred. Please check the log files for details.");
MessageBox.Show("Error Code: " + Str_ExitCode);
}
Scanstate.Close();
}
Basically, I am trying to run the process scanstate.exe. Now, I am trying to run backgroundworker in order to be able to retrieve progress and pass it to the progressbar.
private void btnCapture_Click(object sender, EventArgs e)
{
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Step = 1;
BackgroundWorker CaptureBG = new BackgroundWorker();
CaptureBG.WorkerReportsProgress = true;
CaptureBG.DoWork += new DoWorkEventHandler(MethodToProcess);
CaptureBG.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(CaptureBG_RunWorkerCompleted);
CaptureBG.ProgressChanged += new ProgressChangedEventHandler(CaptureBG_ProgressChanged);
CaptureBG.RunWorkerAsync();
}
and
private void CaptureBG_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs args)
{
progressBar1.Value = 100;
}
private void CaptureBG_ProgressChanged(object sender, ProgressChangedEventArgs args)
{
progressBar1.Value++;
}
However I am either missunderstanding the use or I am missing something, since the process runs, but I don't get any progress on the progressbar. It only fills once the process finish.
What am I doing wrong? In general, how would a process report progress if I don't know exactly how long is going to take?
Thanks in advance

The BackgroundWorker is responsible for updating the progress as it gets further complete with its task.
There is no interaction between your process that you launch and your code that would provide progress of that process back to your code.
In order for this to work, two things have to happen:
You need to define a mechanism for your process to report progress to the BackgroundWorker.
The BackgroundWorker must update its own progress by calling the ReportProgress method so that the ProgressChanged event is fired.
The first step is the tricky one and depends on how scanstate.exe works. Does it do anything to give an indication of progress, such as write to the console? If so, you can redirect the console output and parse that output to determine or at least estimate progress.
UPDATE
Scanstate.exe provides the ability to write progress to a log, e.g.:
scanstate /i:migapp.xml /i:miguser.xml \\fileserver\migration\mystore /progress:prog.log /l:scanlog.log
You could use a FileWatcher in your BackgroundWorker to look for changes to the progress log and update progress accordingly.

Related

Using a background worker in ASP.NET with AJAX

I have the need to perform a background task that has a progress bar that shows percentage done and a cancel button. Task specifics aside, for now, I just want to get an example working, so I just have the three main event handlers (DoWork, ProgressChanged, and RunWorkerCompleted) and a loop that just increments a counter and sleeps for 50ms in DoWork. However, it doesn't update except for once at the end.
In Windows Forms I use a Background worker and it functions correctly without any issues. I'd like to just use this same code. However, I have been seeing stuff that says ASP.NET must use AJAX to get the same functionality. So my questions are:
1) Do I really need AJAX to use the background worker?
2) If yes, I do need AJAX, what is the easiest, most simplest way a person that doesn't know a darn thing about AJAX could do to get the Background worker up and running on an ASP.NET webpage?
3) If no, I don't need AJAX, can anyone point me to a working sample that doesn't use it? I am interested even if it uses some other threading method than background workers.
Sorry for the multi-part question! If you can answer one or the other, it would be much appreciated. I don't really mind which method I end up using as long as it works.
Code for reference from the .cs page:
protected void bwProcess_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lblProgress.Text = "Task Complete: " + e.Result;
}
protected void bwProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
lblProgress.Text = e.ProgressPercentage.ToString();
}
protected void bwProcess_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
if (bwProcess.CancellationPending)
{
lblProgress.Text = "Task Cancelled.";
e.Cancel = true;
return;
}
bwProcess.ReportProgress(i);
Thread.Sleep(50);
}
e.Result = "100%";
}
protected void BWClick(object sender, EventArgs e)
{
lblProgress.Text = "Firing Process...";
bwProcess = new BackgroundWorker();
bwProcess.WorkerReportsProgress = true;
bwProcess.WorkerSupportsCancellation = true;
bwProcess.DoWork += new DoWorkEventHandler(bwProcess_DoWork);
bwProcess.ProgressChanged += new ProgressChangedEventHandler(bwProcess_ProgressChanged);
bwProcess.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwProcess_RunWorkerCompleted);
if (bwProcess != null)
{
bwProcess.RunWorkerAsync("StartAsynchronousProcess");
}
}
Other notes: I have entered Async="True" and EnableSessionState="ReadOnly" into the #Page.
Thanks in advance!
Web programming offer's many challenges which are easy to take for granted when desktop programming. Moving from one to the other can require a lot of changes. Spawning long-running threads is one of those things that require more care to avoid pitfalls. The application pool does not know about threads that you spawn so when it recycles it will kill those threads causing unexpected behavior in your application. See this post for more about that, Can I use threads to carry out long-running jobs on IIS?
This means you need to use a more persistent means to keep track of progress. A database would probably be best, but even a file would persist after the pool is recycled.
AJAX would be perfect for this because it will allow you to pull the progress from the database asynchronously in the background and update the webpage. Here is a breif example of how you might achieve this. All the percentage calculations are done on server side.
function getProgress() {
$.ajax({
url: '/progress', // send HTTP-GET to /progress page
dataType: 'json', // return format will be JSON
success: function(result) { // function to run once the result is pulled
$("#p1").html('Percentage: %' + result);
if (result == "100")
clearInterval(window.progressID);
}
});
}
function startGetProgress() {
window.progressID = setInterval(getProgress, 10000); // update progress every 10 seconds
}
The above example uses JQuery ajax method, http://api.jquery.com/jQuery.ajax/ so you will need to reference the JQuery library.
Whether you are using webforms or mvc you can add a web api controller to handle the ajax request. Let me know if you need me to clarify anything.

Stop execution of a page

I have a task board, some person is working on some task, if task is assigned to another person by his manager the first person who is working on the task board, his execution should be stopped, and a message should be displayed that "This task is assigned to some one else."
I tried using following in page load.
//Code Behind
if (!Owner)
{
SomecontrolsToHide();
MessageDisplay(); // JavaScript function call using RegisterStartupScript()
Response.End();
}
protected void MessageDisplay()
{
string dbMessage = "Task is assigned to someone else.";
ClientScriptManager cs = Page.ClientScript;
cs.RegisterStartupScript(typeof(Page), "ShowMessageWrapup_" + UniqueID, "showMessageDisplay('','" + dbMessage + "');", true);
}
// JavaScript function that displays message.
function showMessageDisplay(args, displayMessage) {
if (displayMessage != "") {
document.getElementById("spanMessage").innerHTML = displayMessage;
document.getElementById("spanMessage").style.display = 'inline';
}
}
It stops the execution but message is not displayed and Controls are not hidden too.
What should I do?
Don't do Response.End(). Just return without doing anything.
This will show the message box. Try this.
Response.Write(#"<script language='javascript'>alert('You are not allowed for this task !!!')</script>");

Can't redirect to another page using ASP.NET and WF 4

I am using WF 4 with ASP.NET and as part of the workflow the system may need to redirect to other pages for the user to input additional information under certain circumstances. Once they have entered that information, the system needs to resume the workflow where it left off.
I have this code so far in the initial page that kicks off the process and an activity in the workflow that sets a bookmark.
static InstanceStore instanceStore;
static AutoResetEvent instanceUnloaded = new AutoResetEvent(false);
static Guid id;
protected void Page_Load(object sender, EventArgs e)
{
SetupInstanceStore();
}
protected void btnStartWorkflow_Click(object sender, EventArgs e)
{
app = Session["applicant"];
Dictionary<string, object> workflowInputs = new Dictionary<string, object>();
workflowInputs.Add("Applicant", app.Applicant);
WorkflowApplication workflowApplication = new WorkflowApplication(new IdentityCheckActivites.IdentityCheckWorkflow(), workflowInputs);
workflowApplication.InstanceStore = instanceStore;
//returning IdleAction.Unload instructs the WorkflowApplication to persist application state and remove it from memory
workflowApplication.PersistableIdle = (a) =>
{
return PersistableIdleAction.Persist;
};
workflowApplication.Unloaded = (a) =>
{
instanceUnloaded.Set();
};
workflowApplication.Completed = (a) =>
{
instanceUnloaded.Set();
};
workflowApplication.Persist();
id = workflowApplication.Id;
workflowApplication.Run();
Session["id"] = id;
workflowApplication.Idle = (a) =>
{
instanceUnloaded.Set();
};
instanceUnloaded.WaitOne();
var bookmarks = workflowApplication.GetBookmarks();
if (bookmarks != null && bookmarks[0].OwnerDisplayName == "CC")
{
workflowApplication.Unload();
Context.Response.Redirect("SecondPage.aspx");
}
Context.Response.Redirect("FinalPage.aspx");
}
private static void SetupInstanceStore()
{
instanceStore = new SqlWorkflowInstanceStore(#"Data Source=xxx;Initial Catalog=SampleInstanceStore;User Id=xxx;Password=xxx;Asynchronous Processing=True");
InstanceHandle handle = instanceStore.CreateInstanceHandle();
InstanceView view = instanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));
handle.Free();
instanceStore.DefaultInstanceOwner = view.InstanceOwner;
}
This seems to work very well in that it persists the workflow to the database and if the bookmark is set I want to redirect to a second page for the user to enter more data.
This is the part of the code that I am having problems with: -
var bookmarks = workflowApplication.GetBookmarks();
if (bookmarks != null && bookmarks[0].OwnerDisplayName == "CC")
{
workflowApplication.Unload();
Context.Response.Redirect("SecondPage.aspx");
}
Context.Response.Redirect("FinalPage.aspx");
If there's a bookmark set, I redirect to an intermediary page, if not and no user intervention was necessary, the page will just redirect to the final page.
This works if the bookmark is set, but if not the workflowApplication.GetBookmarks() statement throws an exception telling me that the workflow has completed.
I can't seem to find a way to detect at this stage which state the workflow is in so that I can redirect to the relevant page.
Maybe I have the wrong idea in general, as much as I search though, I cannot seem to find a lot of guidance on this subject.
Any ideas?
Thanks,
Jim.
I don't think there is a way to directly determine if the workflow is completed from WorkflowApplication (except for catching and inspecting the exception that is thrown).
But you could set a flag in side your Completed delegate which is executed only if the there is no bookmark set and the workflow is completed. You could then check this flag before calling GetBookmarks().
Not sure if I understand exactly, but it seems that your page controller is looking at the state of the workflow to understand what page to redirect to? The problem is that the state may be non-existent if the WF instance has ended?
If the above is correct then perhaps the approach is wrong. A more appropriate approach might be to have a WCF WF service on AppFabric (correlated by session id) handle the website request directly. (If a user in a particular session visits the site, then the WF determines what page to render, and if the user hits a certain button, then send a WCF WF message using net pipe binding)
instead of
workflow.idle
you need
wfApp.PersistableIdle
and don't forget
instanceUnloaded.Set();

Is there any way to speed up crystal reports generation?

We are running a reporting web application that allows the user to select a few fields and a crystal report is generated based off of the fields selected. The SQL that is generated for the most complex report will return the data in < 5 seconds, however it takes the report and average of 3 minutes to run, sometimes longer causing a time out. We are running VS2010. The reports are basically set up out of the box with no real manipulations or computations being done, just displaying the data in a nice format. Is there anything we can try to speed it up, pre-loading a dummy report to load the dlls, some hack to make crystal run faster, anything?
EDIT: Code Added to show the databinding
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string strFile = Server.MapPath(#"AwardStatus.rpt");
CrystalReportSource1.Report.FileName = strFile;
DataTable main = Main();
CrystalReportSource1.ReportDocument.SetDataSource(main);
CrystalReportViewer1.HasCrystalLogo = false;
CrystalReportSource1.ReportDocument.ExportToHttpResponse(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat, Response, false, "pmperformance");
}
}
private DataTable Main()
{
Guid guidOffice = Office;
CMS.Model.ReportsTableAdapters.ViewACTableAdapter rptAdapter = new CMS.Model.ReportsTableAdapters.ViewACTableAdapter();
Reports.ViewAwardedContractsDataTable main = new Reports.ViewAwardedContractsDataTable();
if (Office == new Guid())
{
IEnumerable<DataRow> data = rptAdapter.GetData().Where(d => UserPermissions.HasAccessToOrg(d.guidFromId, AuthenticatedUser.PersonID)).Select(d => d);
foreach (var row in data)
{
main.ImportRow(row);
}
}
else if (guidOffice != new Guid())
{
main = rptAdapter.GetDataByOffice(guidOffice);
}
else
{
main = new Reports.ViewACDataTable();
}
return main;
}
private Guid Office
{
get
{
string strOffice = Request.QueryString["Office"];
Guid guidOffice = BaseControl.ParseGuid(strOffice);
if (!UserPermissions.HasAccessToOrg(guidOffice, AuthenticatedUser.PersonID))
{
return Guid.Empty;
}
else
{
return guidOffice;
}
}
}
protected void CrystalReportSource1_DataBinding(object sender, EventArgs e)
{
//TODO
}
This may be a bit flippant, but possibly consider not using crystal reports... We had a fair bit of trouble with them recently (out of memory errors being one), and we've moved off to other options and are quite happy...
Here's what I would do:
Put clocks from the time you get the field choices from the user, all the way to when you display the report. See where your processing time is going up.
When you look at the clocks, there can be various situations:
If Crystal Reports is taking time to fill the report, check how you're filling it. If you're linking the report fields directly to your data table, CR is probably taking time looking up the data. I suggest creating a new table (t_rpt) with dynamic columns (Field1, Field2,..FieldN) and pointing your report template to that table. I don't know if you're already doing this.
If it's taking time for you to lookup the data itself, I suggest creating a view of your table. Even though a memory hog, this will make your lookup quick and you can delete the view once you're done.
If it's none of the above, let us know what your clocks show.
In terms of loading any large amount of data, you'll always want to use a stored procedure.
Outside of that, you WILL see a delay in the report running the first time the Crystal DLLs load. Yes, you can preload them as you mentioned and that will help some.

Count Number of Visitors in WebSite using ASP.Net and C#

I want to keep track of the number of visitors to my site.
I tried the following code in the Global.asax class,
<script runat="server">
public static int count = 0;
void Application_Start(object sender, EventArgs e)
{
Application["myCount"] = count;
}
void Session_Start(object sender, EventArgs e)
{
count = Convert.ToInt32(Application["myCount"]);
Application["myCount"] = count + 1;
}
</script>
I am retrieving the value in the aspx page as follows:
protected void Page_Load(object sender, EventArgs e)
{
int a;
a = Convert.ToInt32((Application["myCount"]));
Label4.Text = Convert.ToString(a);
if (a < 10)
Label4.Text = "000" + Label4.Text ;
else if(a<100)
Label4.Text = "00" + Label4.Text;
else if(a<1000)
Label4.Text = "0" + Label4.Text;
}
The above coding works fine. It generates the Visitors properly but the problem is when I restart my system, the count variable again starts from 0 which logically wrong.
I want the value of count to be incremented by 1 from the last count value.
So can anyone tell me how to accomplish this task?
Please help me out!
Thanks in advance!
If you want the count to keep incrementing over application restarts, you'll need to store the value somewhere - in a database or a file somewhere, and load that value up when the application starts.
Also, you can use the following to ensure your displayed count is always at least 4 characters:
int a;
a = Convert.ToInt32(Application["myCount"]);
Label4.Text = a.ToString("0000");
See Custom Numeric Format Strings for more info.
Edit to respond to comment
Personally, I'd recommend using a database over writing to the file system, for at least the following reasons:
Depending on your host, setting up a database may well be a lot easier than enabling write access to your file system.
Using a database will allow you to store it as an int rather than a string.
Under heavy traffic, you'll have issues with multiple threads trying to open a text file for write access - which will cause a lock on the file, and cause a bottle neck you don't need.
Various resources will tell you how to connect to a database from your code, a good place to start would be this How To: Connect to SQL Server, and looking into the methods under "What are the alternatives" for details on how to query and update the database.
C# code is show below:
protected void Page_Load(object sender, EventArgs e)
{
this.countMe();
enter code here
DataSet tmpDs = new DataSet();
tmpDs.ReadXml(Server.MapPath("~/counter.xml"));
lblCounter.Text = tmpDs.Tables[0].Rows[0]["hits"].ToString();
}
private void countMe()
{
DataSet tmpDs = new DataSet();
tmpDs.ReadXml(Server.MapPath("~/counter.xml"));
int hits = Int32.Parse(tmpDs.Tables[0].Rows[0]["hits"].ToString());
hits += 1;
tmpDs.Tables[0].Rows[0]["hits"] = hits.ToString();
tmpDs.WriteXml(Server.MapPath("~/counter.xml"));
}
Then you need to have an xml file in the root directory to make the code work as well. The XML file will look like this:
<?xml version="1.0" encoding="utf-8" ?>
<counter>
<count>
<hits>0</hits>
</count>
</counter>
In First Answer U had declare count variable globally,that's why in every new session count starts with 0.for better result ,increment application[] variable inside session_start method.
Usually you use other Tools for that Task (weblog analyser).
As you store your value in Memory (Application["myCount"]) this value will not survive a server restart. So you have to store it in a
database
plain textfile
whatever

Resources