TargetInvocationException was unhandled at the end of DoWork Backgroundworker Method - c#

Here's DoWork:
private void uploadWorker_DoWork(object sender, DoWorkEventArgs e)
{
uploadWorker.ReportProgress(20);
int tiffError = 0;
finalFiles = Directory.GetFiles(AppVars.FinalPolicyImagesFolder);
foreach (string file in finalFiles)
{
if (!file.EndsWith(".tiff"))
{
tiffError = 1;
break;
}
}
uploadWorker.ReportProgress(50);
if (tiffError == 1)
{
MessageBox.Show("There are files in this folder that are not .tiff. Please ensure only .tiff files are in this folder.", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
if (finalFiles.Length == 0)
{
MessageBox.Show("There are no TIFF files to be uploaded. Please generate files first.", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
pbUpload.Value = 0;
EnableAllButtons();
}
else
{
double count = finalFiles.Length;
int current = 0;
int pbValue = 0;
uploadWorker.ReportProgress(70);
foreach (string file in finalFiles)
{
current = current + 2;
if (file.Contains(".tiff") == true)
{
PolicyNumber = Path.GetFileName(file).Split('_')[0];
basePolicyNumber = PolicyNumber.Remove(PolicyNumber.Length - 2);
basePolicyNumber = basePolicyNumber + "00";
finalPolicyName = Path.GetFileName(file);
PolicyUUID = Transporter.GetPolicyUUID(AppVars.pxCentralRootURL, basePolicyNumber);
if (PolicyUUID == "")
{
MessageBox.Show("The InsightPolicyID for the policy you are trying to upload does not exist in ixLibrary. Please ensure the policy number is correct. If you are sure it should be in ixLibray, please contact IT.", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
ixLibrarySourceFileURL = AppVars.ixLibraryPolicyAttachmentsURL + finalPolicyName;
UploadToixLibraryErrorCode = Transporter.UploadFileToixLibrary(AppVars.ixLibraryPolicyAttachmentsURL, file);
if (UploadToixLibraryErrorCode != 0)
{
MessageBox.Show("There was an error uploading the file to ixLibrary. Please contact IT about this problem.", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
GeneratePayLoadErrorCode = Transformer.GeneratePayLoad(ixLibrarySourceFileURL, finalPolicyName);
if (GeneratePayLoadErrorCode != 0)
{
MessageBox.Show("There was an error generating the XML for pxCentral. Please contact IT about this problem.", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
pxCentralPOSTErrorCode = Transporter.pxCentralPOST(AppVars.pxCentralRootURL + PolicyUUID, AppVars.pxCentralXMLPayloadFilePath);
pbValue = Convert.ToInt32(((current / count) * 30) + 70);
uploadWorker.ReportProgress(pbValue);
}
}
}
}
}
}
}
}
As soon as it hits the last }, I get the TargetInvocationException was unhandled error here (see comment in code):
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
bool createdNew = false;
Mutex mutex = new Mutex(true, "MyApplicationMutex", out createdNew);
if (createdNew == true)
{
//error happens here
Application.Run(new frmMain());
}
else
{
MessageBox.Show("The application is already running.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
}
I'm not sure why this started happening all of the sudden. Does anyone know why?
Finally, here's RunWorkerCompleted:
private void uploadWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error == null)
{
DeleteFinalFiles(finalFiles);
MessageBox.Show("Upload process complete.", "Complete!", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
EnableAllButtons();
}

You are calling EnableAllButtons in your DoWork handler. this, presumably, changes the Enabled state of buttons on the form. that is not legal from any other thread than the UI thread. You should make the call to EnableAllButtons in your ProgressChanged event handler or in your RunWorkerCompleted event handler. You're also calling ProgressBar.Value in the DoWork with the code pbUpload.Value = 0.
Also, you should call MessageBox.Show from your UI thread (i.e. in ProgressChanged or RunworkerCompleted handler) so that the MessageBox can be associated with your forms message pump propertly. You should pass a form object to MessageBox.Show to associate the message box with the form so you can't bring the form to the foreground while the message box is shown. e.g.:
MessageBox.Show(this,
"There are files in this folder that are not .tiff. Please ensure only .tiff files are in this folder.",
"Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);

In WinForms, you must only access a control on the thread that created the control. Your DoWork event handler is not running on the thread that created the form (which, of course, is the point). Therefore, you must not access any of the controls on the form in the DoWork handler. Doing so can create unpredictable results.

I had exactly the same problem with TargetInvocation Exception raised after the completion of the background process. Inside the backgroundWorker ProgressChanges Event I have references to controls as shown below`private void m_oWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// This function fires on the UI thread so it's safe to edit
// the UI control directly, no funny business with Control.Invoke :)
CurrentState state =
(CurrentState)e.UserState;
txtProgress.Text = state.CrawlStatus;
lblStatus2.Text = state.sStatus;
txtItemsStored.Text = state.TotItems.ToString() + " items";
txtLastRunTime.Text = state.MostRecentGatherDate.ToString();
AppNameKey.SetValue("LastCrawlTime", txtLastRunTime.Text);
}`
The DoWork event reads from a control
private void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
DateTime LastCrawlTime;
try
{
LastCrawlTime = Convert.ToDateTime(txtLastRunTime.Text);
if (lblStatus2.Text != "Status: Running" || (!cmdRunNow.Enabled && cmdStopRun.Enabled)) // run is not currently running or cmdRunNow clicked
{
//lblStatus2.Text = "Status: Running";
GetUpdated(LastCrawlTime,e);
}
}
catch (Exception Ex)
{
MessageBox.Show(Ex.Message);
}
}
The RunWorkedrCompleted Event writes to a control:
void m_oWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
lblStatus2.Text = "Status: Stopped";
cmdStopRun.Enabled = false;
}
// Check to see if an error occurred in the background process.
else if (e.Error != null)
{
lblStatus2.Text = "Fatal Error while processing.";
}
else
{
// Everything completed normally.
//CurrentState state = (CurrentState)e.UserState;
lblStatus2.Text = "Status: Finished";
}
}
None of these caused problems. What did cause the problem was the attempt to reference e.UserState in the RunWorker_Completed event (commented out above)

I figured out the issue.
The progress bar was exceeding its maximum allowed (of 100).
The problem was that in the code, I was incrementing the progress bar as such:
current = current + 2;
I replaced it with:
current++;
The reason why I was incrementing by 2 was simply for testing purposes.

Related

Correctly create a task for log-in function

I was working on my windows form program, and i saw that the login function (linked to a simple button) freeze my application. I searched on internet and i found how to create a task, but i'm not sure about how it works ...
That's my login function, how can i correctly translate it into a task?
string sURL = url + "/login";
string result = null;
await Task.Run(() =>
{
try
{
result = Web_api.MakeRequest("POST", sURL); //return null if there is some error
}
catch(Exception ex)
{
Debug.WriteLine("[frmLogin] --> result: " + result);
}
});
if(result != null)
{
try
{
Login_response accepted = JsonConvert.DeserializeObject<Login_response>(result);
Debug.WriteLine("[frm_Login] --> accepted: " + accepted);
if (accepted.login)
{
//throw new Exception();
Debug.WriteLine("[frm_login]: result " + result);
frmMain frm = new frmMain(); //calling the new form
frm.Show(); //new form is show-up
this.Hide(); //log-in form hide
frm.FormClosed += Frm_FormClosed; //close the form
}
}
//if server is down, or the id or password is wrong
catch (Exception ex)
{
lblLoginError.Visible = true; //pop-up the error label
pbLogin.Visible = false; //hide the progress-bar
this.Style = MetroFramework.MetroColorStyle.Red; //changing the color of the form
Debug.WriteLine("Exception: " + ex);
}
}
else
{
lblLoginError.Visible = true; //pop-up the error label
pbLogin.Visible = false; //hide the progress-bar
this.Style = MetroFramework.MetroColorStyle.Red; //changing the color of the form
}
EDIT: i provided a real (and working) soluction and i followed all the suggestion in the comments ... do you think this could be acceptable?
Execute any potentially long-running code on a background thread using a Task:
private async void btnLogin_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtUser.Text.Trim()) || string.IsNullOrEmpty(txtPassword.Text.Trim()))
{
MessageBox.Show("You must insert a valid user/password format", "Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
//Progress bar start
pbLogin.Visible = true; // BUT THIS PROGRESS BAR I STACK DUE TO STATIC DEFINITON OF LOGIN
//Getting ID + Password
User.username = txtUser.Text;
User.password = txtPassword.Text;
string sURL = Web_api.url + "/login";
try
{
Login_response accepted = await Task.Run(() =>
{
//the following code gets executed on a background thread...
string result = Web_api.MakeRequest("POST", sURL);
Login_response accepted = JsonConvert.DeserializeObject<Login_response>(result);
Debug.WriteLine("[frm_Login] --> accepted: " + accepted);
return accepted;
});
//...and here you are back on the UI thread once the task has completed
if (accepted.login)
{
//throw new Exception();
Debug.WriteLine("[frm_login]: result " + result);
frmMain frm = new frmMain(); //calling the new form
frm.Show(); //new form is show-up
this.Hide(); //log-in form hide
frm.FormClosed += Frm_FormClosed; //close the form
}
}
//if server is down, or the id or password is wrong
catch (Exception ex)
{
lblLoginError.Visible = true; //pop-up the error label
pbLogin.Visible = false; //hide the progress-bar
this.Style = MetroFramework.MetroColorStyle.Red; //changing the color of the form
Debug.WriteLine("Exception: " + ex);
}
}
Event handlers always return void. They are an exception to the rule that says that an async method always should return a Task or a Task<T>.
You can create an async void method. It is actually the correct way to implement async callbacks for events such as button click.
First, let's make an asynchronous login method :
public async Task LoginAsync()
{
try
{
var stream = await _http.GetStreamAsync($"{baseUrl}/login");
var response = await JsonSerializer.DeserializeAsync<LoginResponse>(stream);
if (!response.login)
{
throw new BusinessException<LoginError>(LoginError.LoginDenied);
}
}
catch (HttpRequestException ex)
{
throw new BusinessException<LoginError>(LoginError.LoginFailed, ex);
}
}
Now we implement an asynchronous button callback:
private async void btnLogin_Click(object sender, EventArgs e)
{
try
{
await authentication.LoginAsync().ConfigureAwait(false);
// handle login success here
}
catch (BusinessException<LoginError> ex) when (ex.Error == LoginError.LoginDenied)
{
// handle login denied here
}
catch (BusinessException<LoginError> ex) when (ex.Error == LoginError.LoginFailed)
{
// handle connection failed here
}
}
If you want the LoginAsync() method to do some UI operations (for instance showing a form), you will need to use ConfigureAwait(true). Otherwise, there is a risk that part of the method is executed on a different thread and UI operations will fail.

How do I bypass code in the RunWorkerCompleted event if an error occurs?

I wrote a program that creates an XML file from the contents of an Excel spreadsheet. The program works and I am now making it more robust by adding error checking. For example, if the Excel spreadsheet does not contain a required XML tag, the program displays an error and returns from the main program. When an error occurs, the RunWorkerCompleted() event fires, and code executes that shouldn't execute because an error occurred. My question is how to determine if an error occurred so I can bypass certain code in the RunWorkerCompleted() event.
I tried calling bgw.CancelAsync() in the main program where the error is detected, but RunWorkerCompletedEventArgs e.Cancelled is false so it doesn't work.
Here are some excerpts from my code. The progress bar is started in the click event of a button. It works, but I had to declare cellsProcessed, rowCount, and colCount as global because I couldn't figure out how to pass them to bgw_DoWork().
private void button_create_Click(object sender, EventArgs e)
{
// Define the event that fires when the progress bar finishes
bgw.RunWorkerCompleted += bgw_Complete;
rowCount = xmlRange.Rows.Count; // Declared as global
colCount = xmlRange.Columns.Count; // Declared as global
// Start the progress bar thread
if (!bgw.IsBusy)
{
bgw.RunWorkerAsync();
}
// Read the header in row 1 and return an error if it isn't valid
for (colIdx = 1; colIdx <= colCount; colIdx++)
{
if ((xmlRange.Cells[1, colIdx] != null) && (xmlRange.Cells[1, colIdx].Value2 != null))
{
cellContents = xmlRange.Cells[1, colIdx].Value2.ToString();
switch (colIdx)
{
case 1:
if (cellContents != "Tag")
{
error = true;
errText = "Cell A1 must contain 'Tag'";
}
break;
case 2:
if (cellContents != "Type")
{
error = true;
errText = "Cell B1 must contain 'Type'";
}
break;
if (error)
{
bgw.CancelAsync();
MessageBox.Show(errText, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
// Close Excel resources
xmlWorkbook.Close();
xmlSpreadsheet.Quit();
Marshal.ReleaseComObject(xmlRange);
Marshal.ReleaseComObject(xmlWorksheet);
Marshal.ReleaseComObject(xmlWorkbook);
Marshal.ReleaseComObject(xmlSpreadsheet);
GC.Collect();
return;
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
if (bgw.CancellationPending)
{
e.Cancel = true;
}
else
{
label_pctComplete.Text = "Completed " + progressBar.Value.ToString() + "%";
bgw.ReportProgress((100 * ++cellsProcessed) / (rowCount * colCount));
}
}
private void bgw_Complete(object sender, RunWorkerCompletedEventArgs e)
{
if (!e.Cancelled)
{
label_pctComplete.Visible = false;
progressBar.Visible = false;
// Inform the user that the XML file was created
label_created.Visible = true;
label_created.Text = "Done!...Created " + textBox_outputFile.Text;
// Enable the button so the user can view the XML Limit File
button_openLimitFile.Enabled = true;
}
}
private void bgwProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
Thank you for any help.

How to open form inside method, submit button and then come back to original method and continue?

Our application requires that a witness must authenticate before a logged in user can perform enrollment operations (Enroll and Delete).
This is not an issue for Enrolling as I can add a check (IsWitnessApproved) to an enrollment_OnStartEnroll method I.E. before the Capture method is called and fired.
However, this is not possible for Deletion as I don't have access to a point where the enrollment_OnDelete method hasn't fired.
I haven't been able to get a response to this issue from Digital Persona so I'm now looking at work-arounds.
I'm exploring if its possible to open up a new form (WitnessApproval) inside the enrollment_OnDelete method, approve the witness in the form (btnConfirmWitness_Click) and then come back into the method and continue on with the deletion?
enrollment_OnDelete method:
private void enrollment_OnDelete(DPCtlUruNet.EnrollmentControl enrollmentControl, Constants.ResultCode result, int fingerPosition)
{
if (!witnessApproved)
{
WitnessApproval witnessApproval = new WitnessApproval();
witnessApproval.Show();
}
else
{
int fingerMask = GetFingerMask(fingerPosition);
if (enrollmentControl.Reader != null)
{
try
{
// Delete from database
new EnrollmentDAL().DeleteEnrolledFingerprint(Settings.Default.Username, fingerMask, txt_WitnessName.Text);
MessageBox.Show("Fingerprint deleted.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information);
pbFingerprint.Image = null;
pbFingerprint.Visible = false;
btnCancel.Visible = false;
witnessApproved = false;
txt_WitnessName.Text = String.Empty;
txt_WitnessPassword.Text = String.Empty;
}
catch (Exception ex)
{
MessageBox.Show("There was a problem deleting the fingerprint.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
new Util().LogError(ex);
}
}
else
{
MessageBox.Show("No Reader Connected.", "Message", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
_sender.Fmds.Remove(fingerPosition);
}
}
Selected WitnessApproval methods:
private void btnConfirmWitness_Click(object sender, EventArgs e)
{
lbl_Validation.Visible = false;
if (txt_WitnessName.Text == String.Empty)
{
SetMessage("Please enter a Witness.");
return;
}
if (txt_WitnessPassword.Text == String.Empty)
{
SetMessage("Please enter a Password.");
return;
}
if (txt_WitnessName.Text == Settings.Default.Username)
{
SetMessage("User and witness cannot be the same.");
return;
}
bool IsValidate = Membership.ValidateUser(txt_WitnessName.Text, txt_WitnessPassword.Text);
Settings.Default.WitnessName = txt_WitnessName.Text;
Settings.Default.WitnessPassword = txt_WitnessPassword.Text;
if (IsValidate)
{
this.Close();
// Allow enrollment operations
}
else
{
SetMessage("Witness credentials invalid.");
}
}
private void btnCancelWitness_Click(object sender, EventArgs e)
{
this.Close();
// DO NOT Allow enrollment operations
witnessCancelled = true;
}
private void SetMessage(string message)
{
lbl_Validation.Visible = true;
lbl_Validation.Text = message;
}
How to open form inside method, submit button and then come back to original method and continue?
There is ShowDialog method for this purposes.
Here is usage example from MSDN:
public void ShowMyDialogBox()
{
Form2 testDialog = new Form2();
// Show testDialog as a modal dialog and determine if DialogResult = OK.
if (testDialog.ShowDialog(this) == DialogResult.OK)
{
// Read the contents of testDialog's TextBox.
this.txtResult.Text = testDialog.TextBox1.Text;
}
else
{
this.txtResult.Text = "Cancelled";
}
testDialog.Dispose();
}
In your case, Form2 is WitnessApproval.
In WitnessApproval Form button handlers you will also need to set DialogResult to true when the witness is approved or to false when user cancelled operation.

How to implement "retry/abort" mechanism for writing files that may be used by another process?

This is really short question. I don't understand try-catch mechanism completely.
This is my current code:
public static void WriteText(string filename, string text)
{
try
{
System.IO.StreamWriter file = new System.IO.StreamWriter(filename);
file.Write(text);
file.Close();
}
catch(Exception exc)
{
MessageBox.Show("File is probably locked by another process.");
}
}
Background:
Im writing application that shares configuration files with another application.
I need some dialog messagebox with "retry" and "abort" buttons, when that file is used by other application. When that message will appear - I will close that other application and I will try to rewrite that file again by pressing "Retry" button.
Whatr we have is using a counter for re-tries and possibly a thread sleep.
So something like
int tries = 0;
bool completed = false;
while (!completed)
{
try
{
System.IO.StreamWriter file = new System.IO.StreamWriter(filename);
file.Write(text);
file.Close();
completed = true;
}
catch(Exception exc)
{
tries++;
//You could possibly put a thread sleep here
if (tries == 5)
throw;
}
}
Even though there's a good answer already I'll submit one that's more tuned towards the OP's question (let the user decide instead of using a counter).
public static void WriteText(string filename, string text)
{
bool retry = true;
while (retry)
{
try
{
System.IO.StreamWriter file = new System.IO.StreamWriter(filename);
file.Write(text);
file.Close();
retry=false;
}
catch(Exception exc)
{
MessageBox.Show("File is probably locked by another process.");
// change your message box to have a yes or no choice
// yes doesn't nothing, no sets retry to false
}
}
}
If you need more info on how to implement the messagebox check out the following links;
http://msdn.microsoft.com/en-us/library/0x49kd7z.aspx
MessageBox Buttons?
I would do it like that:
public static void WriteText(string filename, string text, int numberOfTry = 3, Exception ex = null)
{
if (numberOfTry <= 0)
throw new Exception("File Canot be copied", ex);
try
{
var file = new System.IO.StreamWriter(filename);
file.Write(text);
file.Close();
}
catch (Exception exc)
{
WriteText(filename,text,--numberOfTry,ex);
}
}
I like it more like this (example tries to save a RichTextBox on close and allows retrying save or aborting close):
protected override void OnClosing(CancelEventArgs e)
{
if (richTextBox_Query.Modified)
{
DialogResult result;
do
try
{
richTextBox_Query.SaveFile(
Path.ChangeExtension(Application.ExecutablePath, "sql"),
RichTextBoxStreamType.UnicodePlainText);
result = DialogResult.OK;
richTextBox_Query.Modified = false;
}
catch (Exception ex)
{
result = MessageBox.Show(ex.ToString(), "Exception while saving sql query",
MessageBoxButtons.AbortRetryIgnore);
e.Cancel = result == DialogResult.Abort;
}
while (result == DialogResult.Retry);
}
base.OnClosing(e);
}

Value of volatile variable doesn't change in multi-thread

I have a winform application that runs in background with a BackgroundWorker that has an infinite loop that execute something every hour. My UI Form class is something like this:
public partial class frmAutoScript : Form
{
private volatile bool _isDownloading = false;
private bool IsDownloading { get { return this._isDownloading; } set { this._isDownloading = value; } }
public frmAutoScript()
{
InitializeComponent();
this.RunAutoSynchronization();
}
private void RunAutoSynchronization()
{
bool isDownloading = this.IsDownloading;
BackgroundWorker bgwDownloader = new BackgroundWorker();
bgwDownloader.WorkerReportsProgress = true;
bgwDownloader.ProgressChanged += (sndr, evnt) =>
{
if (evnt.ProgressPercentage == 2)
isDownloading = this.IsDownloading;
else
{
this.IsDownloading = evnt.ProgressPercentage == 1;
isDownloading = this.IsDownloading;
}
};
bgwDownloader.DoWork += (sndr, evnt) =>
{
while (true)
{
if (DateTime.Now.Hour == 16 &&
DateTime.Now.Minute == 0)
{
try
{
bgwDownloader.ReportProgress(2);
if (!isDownloading)
{
bgwDownloader.ReportProgress(1);
new Downloader().Download();
}
bgwDownloader.ReportProgress(0);
}
catch { }
}
System.Threading.Thread.Sleep(60000);
}
};
bgwDownloader.RunWorkerAsync();
}
}
And in that frmAutoScript, I also have a button named btnDownload that when clicked, it will download and change the value of the volatile varialbe _isDownloading. The event of the button is something like this:
private void btnDownload_Click(object sender, EventArgs e)
{
if (IsDownloading)
MessageBox.Show("A download is currently ongoing. Please wait for the download to finish.",
"Force Download", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
else
{
this.IsDownloading = true;
BackgroundWorker bgwDownloader = new BackgroundWorker();
bgwDownloader.DoWork += (sndr, evnt) =>
{
try
{
new Downloader().Download();
}
catch(Exception ex)
{
MessageBox.Show("An error occur during download. Please contact your system administrator.\n Exception: " +
ex.GetType().ToString() + "\nError Message:\n" + ex.Message + " Stack Trace:\n" + ex.StackTrace, "Download Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
};
bgwDownloader.RunWorkerCompleted += (sndr, evnt) =>
{
this.IsDownloading = false;
};
bgwDownloader.RunWorkerAsync();
}
}
But when I click the button btnDownload and the _isDownloading is set to true, and when the system time hit the 4:00 PM, the new Downloader().Download(); is executed again eventhough the _isDownloading is set to true. Why was it like this?
My code is in C#, framework 4, project is in winforms, build in Visual Studio 2010 Pro.
Your code is not testing against the volatile field - it is testing against isDownloading, which looks like a "local", but (because it is captured) is in fact a regular (non-volatile) field. So: either use some kind of memory barrier, or force that to be a volatile read. Or more simply: get rid of isDownloading completely, and check against the property.
Incidentally, the cache-defeating properties of volatile are not the intent of the keyword, but rather: a consequence. It'll work, but personally I'd suggest writing the code to work by intent rather than by consequence, perhaps using either a simple lock or something like Interlocked.

Categories

Resources