I am writing a CluedIn Crawler and when I get data, I get a record that contains an array of referenced records. The code looks like this:
public IEnumerable<object> GetData(CrawlJobData jobData)
{
if (!(jobData is MyCrawlJobData myCrawlJobData))
{
yield break;
}
var client = clientFactory.CreateNew(myCrawlJobData);
foreach (var myModel in client.GetMyModels())
{
yield return myModel;
}
}
}
Then in the clue producer, I want to create a main clue and a clue per each referenced record. But the problem is that the override for the MakeClueImpl returns only one Clue:
protected override Clue MakeClueImpl([NotNull] MyModel input, Guid accountId)
{
// ...
}
What can I do to avoid this limitation?
As the GetData is IEnumerable<object>, you can yield different model objects just from this method:
public IEnumerable<object> GetData(CrawlJobData jobData)
{
if (!(jobData is MyCrawlJobData myCrawlJobData))
{
yield break;
}
var client = clientFactory.CreateNew(myCrawlJobData);
foreach (var myModel in client.GetMyModels())
{
yield return myModel;
foreach (var relatedRecordModel in myModel.RelatedRecords)
{
yield return relatedRecordModel;
}
}
}
And then have two clue producers like:
protected override Clue MakeClueImpl([NotNull] MyModel input, Guid accountId)
{
// TODO:
return clue;
}
and
protected override Clue MakeClueImpl([NotNull] MyRelatedRecordModel input, Guid accountId)
{
// TODO:
return clue;
}
Yet another way would be just to produce and yield clues just from the GetData method and then create a dummy clue producer that will accept a Clue:
public IEnumerable<object> GetData(CrawlJobData jobData)
{
if (!(jobData is MyCrawlJobData myCrawlJobData))
{
yield break;
}
var client = clientFactory.CreateNew(myCrawlJobData);
foreach (var myModel in client.GetMyModels())
{
yield return myModel;
foreach (var relatedRecord in myModel.RelatedRecords)
{
// return a Clue per related record
yield return MakeRelatedRecordClue(relatedRecord);
}
}
}
protected override Clue MakeClueImpl([NotNull] Clue input, Guid accountId)
{
return clue;
}
Related
I have the same issue as the below link but with a different graph (APPrintChecks)
how-do-i-override-pxfilteredprocessingjoin-in-a-graph-extension-without-altering
I am overriding the main view to pull in the remittance name from APContact to show in the grid.
[PXFilterable]
public PXFilteredProcessingJoin<APPayment, PrintChecksFilter,
InnerJoin<Vendor, On<Vendor.bAccountID, Equal<APPayment.vendorID>>,
InnerJoin<APContact, On<APContact.contactID, Equal<APPayment.remitContactID>>>>,
Where<boolTrue, Equal<boolTrue>>,
OrderBy<Asc<Vendor.acctName, Asc<APPayment.refNbr>>>> APPaymentList;
However, I do not know how to override the delegate so I won't have the same problem as the other poster (no filter being applied).
protected virtual IEnumerable appaymentlist()
{
if (cleared)
{
foreach (APPayment doc in APPaymentList.Cache.Updated)
{
doc.Passed = false;
}
}
foreach (PXResult<APPayment, Vendor, PaymentMethod, CABatchDetail> doc in PXSelectJoin<APPayment,
InnerJoinSingleTable<Vendor, On<Vendor.bAccountID, Equal<APPayment.vendorID>>,
InnerJoin<PaymentMethod, On<PaymentMethod.paymentMethodID, Equal<APPayment.paymentMethodID>>,
LeftJoin<CABatchDetail, On<CABatchDetail.origModule, Equal<BatchModule.moduleAP>,
And<CABatchDetail.origDocType, Equal<APPayment.docType>,
And<CABatchDetail.origRefNbr, Equal<APPayment.refNbr>>>>>>>,
Where2<Where<APPayment.status, Equal<APDocStatus.pendingPrint>,
And<CABatchDetail.batchNbr, IsNull,
And<APPayment.cashAccountID, Equal<Current<PrintChecksFilter.payAccountID>>,
And<APPayment.paymentMethodID, Equal<Current<PrintChecksFilter.payTypeID>>,
And<Match<Vendor, Current<AccessInfo.userName>>>>>>>,
And<APPayment.docType, In3<APDocType.check, APDocType.prepayment, APDocType.quickCheck>>>>.Select(this))
{
yield return new PXResult<APPayment, Vendor>(doc, doc);
if (_copies.ContainsKey((APPayment)doc))
{
_copies.Remove((APPayment)doc);
}
_copies.Add((APPayment)doc, PXCache<APPayment>.CreateCopy(doc));
}
}
There are other private variables that are referenced in this. Any help appreciated.
Also, if there's a simpler way to pull in a related value on a grid like this (virtual field in DAC?) I'm not stuck on doing it with a graph extension.
So this appears to work but it seems messy and duplicates a lot of code and private variables. Appreciate any feedback if there's a better way to do this:
public class APPrintChecks_Extension : PXGraphExtension<APPrintChecks> {
[PXFilterable]
public PXFilteredProcessingJoin<APPayment, PrintChecksFilter,
InnerJoin<Vendor, On<Vendor.bAccountID, Equal<APPayment.vendorID>>,
InnerJoin<APContact, On<APContact.contactID, Equal<APPayment.remitContactID>>>>,
Where<boolTrue, Equal<boolTrue>>,
OrderBy<Asc<Vendor.acctName, Asc<APPayment.refNbr>>>> APPaymentList;
public IEnumerable appaymentlist()
{
if (cleared)
{
foreach (APPayment doc in APPaymentList.Cache.Updated)
{
doc.Passed = false;
}
}
foreach (PXResult<APPayment, Vendor, APContact, PaymentMethod, CABatchDetail> doc in PXSelectJoin<APPayment,
InnerJoinSingleTable<Vendor, On<Vendor.bAccountID, Equal<APPayment.vendorID>>,
InnerJoin<APContact, On<APContact.contactID, Equal<APPayment.remitContactID>>,
InnerJoin<PaymentMethod, On<PaymentMethod.paymentMethodID, Equal<APPayment.paymentMethodID>>,
LeftJoin<CABatchDetail, On<CABatchDetail.origModule, Equal<BatchModule.moduleAP>,
And<CABatchDetail.origDocType, Equal<APPayment.docType>,
And<CABatchDetail.origRefNbr, Equal<APPayment.refNbr>>>>>>>>,
Where2<Where<APPayment.status, Equal<APDocStatus.pendingPrint>,
And<CABatchDetail.batchNbr, IsNull,
And<APPayment.cashAccountID, Equal<Current<PrintChecksFilter.payAccountID>>,
And<APPayment.paymentMethodID, Equal<Current<PrintChecksFilter.payTypeID>>,
And<Match<Vendor, Current<AccessInfo.userName>>>>>>>,
And<APPayment.docType, In3<APDocType.check, APDocType.prepayment, APDocType.quickCheck>>>>.Select(Base))
{
yield return new PXResult<APPayment, Vendor, APContact>(doc, doc, doc);
if (_copies.ContainsKey((APPayment)doc))
{
_copies.Remove((APPayment)doc);
}
_copies.Add((APPayment)doc, PXCache<APPayment>.CreateCopy(doc));
}
}
private bool cleared;
public void Clear()
{
Base.Filter.Current.CurySelTotal = 0m;
Base.Filter.Current.SelTotal = 0m;
Base.Filter.Current.SelCount = 0;
cleared = true;
Base.Clear();
}
private readonly Dictionary<object, object> _copies = new Dictionary<object, object>();
}
Per Rick's suggestion I implemented the FieldSelecting method. Much, much simpler/cleaner code. It does in fact cause a round trip to the database for each row when using this in a grid column, however, for check printing this should be acceptable. Thanks Rick! Code below.
protected void APPayment_UsrRemitTo_FieldSelecting(PXCache cache, PXFieldSelectingEventArgs e)
{
var row = (APPayment)e.Row;
// fill usrRemitTo from APContact
if (row != null)
{
var extension = PXCache<APRegister>.GetExtension<APRegisterExt>(row);
using (PXConnectionScope cs = new PXConnectionScope())
{
APContact rec = PXSelectReadonly<APContact, Where<APContact.contactID, Equal<Required<APPayment.remitContactID>>>>.Select(Base, row.RemitContactID);
if (rec != null)
{
string remitToName = (!string.IsNullOrEmpty(rec.FullName)) ? rec.FullName : "";
e.ReturnValue = remitToName;
}
else
{
e.ReturnValue = "";
}
}
}
}
Using DBScalar, you can further simplify your event with just one line :
public class APPaymentExt : PXCacheExtension<APPayment>
{
#region UsrRemitTo
[PXString(100)]
[PXUIField(DisplayName="Remit To")]
[PXDBScalar(typeof(Search<APContact.fullName,Where<APContact.contactID, Equal<APPayment.remitContactID>>>))]
public virtual string UsrRemitTo { get; set; }
public abstract class usrRemitTo : PX.Data.BQL.BqlString.Field<usrRemitTo> { }
#endregion
}
I'm pretty new with concurency and I'v hit the wall several times already.
Code pretty much describes everything, but just to clarify: user press the button, application send query to db and in the meantime statusLabel is set to:
Veryfing.
200ms
Veryfing..
200ms
Veryfing...
200ms
Result of query
I'v managed to achieve that, but now, I need to use result of query in another class (if it succeed, another window is opened), but It never does. I came to conclusion that it just checks the result before Task is finished so result is always false, I have no idea how to work around this, so another class checks condition once Task is done.
First, my Authorization class
public class Authorization {
private static String query = "";
private static boolean isValid;
private static Task<Void> task;
public static void verifyLogin(String username, String password) throws SQLException{
Status.get().unbind();
isValid = false;
task = new Task<Void>() {
#Override
protected Void call() throws SQLException {
while(!isCancelled()) {
try {
updateMessage("Weryfikacja.");
Thread.sleep(200);
updateMessage("Weryfikacja..");
Thread.sleep(200);
updateMessage("Weryfikacja...");
Thread.sleep(200);
if(username.equals("") || password.equals("")) {
task.cancel();
updateMessage("Pola nie mogą być puste");
} else {
query = "SELECT login FROM users WHERE login = ?";
Query.execute(query, username);
if(!Query.resultSet.next()) {
task.cancel();
updateMessage("Nie ma takiego użytkownika");
} else {
query = "SELECT password FROM users WHERE login = ?";
Query.execute(query, username);
if(Query.resultSet.next()) {
String passwordValue = Query.resultSet.getString(1);
if(!password.equals(passwordValue)) {
task.cancel();
updateMessage("Podane hasło jest błędne");
} else {
task.cancel();
updateMessage("");
isValid = true;
}
}
}
}
} catch(InterruptedException e) {
if(isCancelled()) {
break;
}
}
}
return null;
}
};
Status.get().bind(task.messageProperty());
new Thread(task).start();
}
public static boolean isValid() {
return isValid;
}
}
called from another class
private void login() {
if( SqlConnection.isConnected()) {
try{
Authorization.verifyLogin(String.valueOf(loginInput.getText()), String.valueOf(passwordInput.getText()));
if(Authorization.isValid()) {
//should go to next menu
//but never does
}
} catch (SQLException e) {
e.printStackTrace();
Debug.log(e.toString());
}
}
}
edit#
Sorry for polish in updateMessage().
Your verifyLogin() method simply starts the verification process in another thread, and then exits immediately. The isValid flag will not be changed until that thread completes, which happens quite a lot later. If you want to do the verification process and then do something else, it doesn't really make sense to manage the threads in verifyLogin().
I don't really understand a lot of what your code is supposed to be doing; you have a while(...) loop, which as far as I can tell can only be executed once (so is redundant). You also seem to execute two SQL queries which are essentially the same. (The first checks to see if there is a row with a certain condition, then if there is, the second retrieves that row. Why not just retrieve the row and check if it is there?)
I would refactor this so that the validateLogin() method doesn't handle the threading at all, and just returns the result of the validation (e.g. a status string, but maybe something else would be appropriate).
/**
* #return An empty string if the login is valid, or an error message otherwise
*/
public static String verifyLogin(String username, String password) throws SQLException{
isValid = false ;
if(username.equals("") || password.equals("")) {
return "Pola nie mogą być puste";
}
query = "SELECT login, password FROM users WHERE login = ?";
Query.execute(query, username);
if(!Query.resultSet.next()) {
return "Nie ma takiego użytkownika";
}
String passwordValue = Query.resultSet.getString(2);
if(!password.equals(passwordValue)) {
return "Podane hasło jest błędne" ;
}
isValid = true;
return "" ;
}
Now I would manage the threads from the login() method. That way you can use the task's onSucceeded handler to execute code when the task completes:
private void login() {
if( SqlConnection.isConnected()) {
Task<String> verifyTask = new Task<String>() {
#Override
protected String call() throws SQLException {
return Authorization.verifyLogin(loginInput.getText(), passwordInput.getText());
}
};
// probably better to use a progress indicator or similar here, but:
Animation animation = new Timeline(
new KeyFrame(Duration.ZERO, e -> Status.get().set("Weryfikacja.")),
new KeyFrame(Duration.millis(200), e -> Status.get().set("Weryfikacja..")),
new KeyFrame(Duration.millis(400), e -> Status.get().set("Weryfikacja...")),
new KeyFrame(Duration.millis(600)));
animation.setCycleCount(Animation.INDEFINITE);
verifyTask.setOnSucceeded(event -> {
animation.stop();
Status.get().set(verifyTask.getValue());
if(Authorization.isValid()) { // or if (verifyTask.getValue().isEmpty())
// go to next menu
}
});
verifyTask.setOnFailed(event -> {
animation.stop();
verifyTask.getException().printStackTrace();
Debug.log(verifyTask.getException().toString());
}
animation.play();
new Thread(verifyTask()).start();
}
}
I'm trying to retrieve user data from Parse (xamarin.ios using c#). I'm using an async method with await. My challenge is,each time I navigate to the tableView in the app, which should populate the user data in question,the table is always empty.
I would like to wait until the results have been returned before proceeding with the other portion of code.I have tried to use the ContinueWith() function but constantly ran into a build error -
Cannot implicitly convert type 'void' to System.Collections.Generic.IEnumerable<Parse.ParseObject>
My Questions:
Is this the best way to wait for the result?
How do I solve the build error?
Here is my current implementation:
public async void retrieveData(string username)
{
try
{
this.requests.ClearRequests();
refreshed = false;
var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);
IEnumerable<ParseObject> results = await query.FindAsync().ContinueWith(t =>{
if(results != null)
{
foreach(ParseObject parseObject in results)
{
UserRequest request = new UserRequest();
request.objectId = parseObject.ObjectId;
request.make = parseObject.Get<string> ("item1");
request.model = parseObject.Get<string> ("item2");
request.year = parseObject.Get<string> ("item3");
request.userName = parseObject.Get<string> ("username");
this.requests.addRequest (request);
}
refreshed = true;
}
});
}
catch(ParseException e) {
Console.WriteLine (e.Message + e.StackTrace);
}
}
You shouldn't need a ContinueWith...that's what the await should handle.
await waits on a Task and then brings back the result with the proper return type. ContinueWith returns a Task, so you would have to grab the Result from the task to make it usable.
For more on this type of thing, you may want to check out Difference between await and ContinueWith
You can try something like this.
public async void retrieveData(string username, )
{
try
{
this.requests.ClearRequests();
refreshed = false;
var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);
IEnumerable<ParseObject> results = await query.FindAsync();
if(results != null)
{
foreach(ParseObject parseObject in results)
{
UserRequest request = new UserRequest();
request.objectId = parseObject.ObjectId;
request.make = parseObject.Get<string> ("item1");
request.model = parseObject.Get<string> ("item2");
request.year = parseObject.Get<string> ("item3");
request.userName = parseObject.Get<string> ("username");
this.requests.addRequest (request);
}
refreshed = true;
}
//This is your refresh method for your TableView
this.RefreshTableView();
//or, if in iOS
NSNotificationCenter.DefaultCenter.PostNotificationName("resultsRetrieved", null);
}
catch(ParseException e) {
Console.WriteLine (e.Message + e.StackTrace);
}
}
To show the results in the tableView, I would recommend moving the refreshing of the tableView to a separate method that gets triggered synchronously after the results have been retrieved and parsed. This is shown with the this.RefreshTableView() call above.
If in iOS on Xamarin, another option is to post a notification to the NSNotificationCenter (the Xamarin documentation for which is here). Use the PostNotificationName part seen above instead and then add an observer in the ViewControllers that you want to be dependent on the data. This is done as follows:
Make a notificationToken object:
NSObject notificationToken;
Then in your setup method (you could put this inside of your ViewDidLoad):
void Setup ()
{
notificationToken = NSNotificationCenter.DefaultCenter.AddObserver ("resultsRetrieved", RefreshData);
}
Make your RefeshData method:
void RefreshData (NSString notifString)
{
this.tableView.ReloadData();
}
And then, make sure you dispose of the notification observer when you tear down the class
void Teardown ()
{
NSNotificationCenter.DefaultCenter.RemoveObserver (notificationToken);
}
I had a similar issue so started using callbacks. I'm using them in Xamarin.Android, pretty sure they're available in Xamarin.iOS.
Method that starts the task method - Note I am passing in a method of this class as a parameter
private async void updatedData()
{
await Utils.DataTasks.getNewLiveTips(populateTipsList);
}
Method that calls for data from server
public class DataTasks
{
public static async Task getAllData(Action<IEnumerable<ParseObjects>> callback) {
var query = new ParseQuery<ParseObjects>().OrderByDescending("updatedAt").Limit(5);
IEnumerable<ParseObjects> parseTips = await query.FindAsync();
foreach (var tip in parseTips)
{
// Save data to DB if needed
}
callback(parseTips);
}
Method I passed as parameter in the first instance is now called
private void populateTipsList(IEnumerable<ParseObjects> results)
{
mAdapter = new TipAdapter(this.Activity, results);
mRecyclerView.SetAdapter(mAdapter);
refresher.Refreshing = false;
}
I got some questions regarding the use of threads, specially when you have to wait for a thread to be finished so you can perform other operations.
In my app, I use threads for operations such as http connections or when I read from or write to a RecordStore.
For example in the following class that I use to initialize my thread, I retrieve some customers from a webservice using the method called HttpQueryCustomers.
public class thrLoadCustomers implements Runnable {
private RMSCustomer mRMSCustomer;
private String mUrl;
public thrLoadCustomers(RMSCustomer rmsCust, String url) {
mRMSCustomer = rmsCust;
mUrl = url;
}
public void run() {
String jsonResultados = "";
try {
jsonResultados = HttpQueryCustomers();
} catch (IOException ex) {
//How to show a message from here??
} catch (SecurityException se) {
//How to show a message here??
} catch (NullPointerException npe) {
//How to show a message from here??
}
if (!jsonResultados.equals("")) {
try {
mRMSCustomer.save(jsonResultados);
} catch (RecordStoreException ex) {
//How to show a message from here???
}
}
}
public String HttpQueryCustomers() throws IOException,SecurityException,NullPointerException {
StringBuffer stringBuffer = new StringBuffer();
HttpConnection hc = null;
InputStream is = null;
System.out.println(mUrl);
try {
hc = (HttpConnection) Connector.open(mUrl);
if (hc.getResponseCode() == HttpConnection.HTTP_OK) {
is = hc.openInputStream();
int ch;
while ((ch = is.read()) != -1) {
stringBuffer.append((char) ch);
}
}
} finally {
is.close();
hc.close();
}
String jsonData = stringBuffer.toString();
return jsonData.toString();
}
}
Notice in the above class that I pass a parameter called rmsCust of the type RMSCustomer
RMSCustomer is a class that I use to handle all the operations related to RMS:
public class RMSCustomer {
private String mRecordStoreName;
private Customer[] mCustomerList;
public RMSCustomer(String recordStoreName) {
mRecordStoreName = recordStoreName;
}
public Customer[] getCustomers() {
return mCustomerList;
}
public Customer get(int index) {
return mCustomerList[index];
}
public void save(String data) throws RecordStoreException,JSONException,NullPointerException {
RecordStore rs = null;
int idNuevoRegistro;
String stringJSON;
try {
rs = RecordStore.openRecordStore(mRecordStoreName, true);
JSONArray js = new JSONArray(data);
//Set the size of the array
mCustomerList = new Customer[js.length()];
for (int i = 0; i < js.length(); i++) {
JSONObject jsObj = js.getJSONObject(i);
stringJSON = jsObj.toString();
idNuevoRegistro = addRecord(stringJSON, rs);
//Add a new Customer to the array
mCustomerList[i] = initializeCustomer(stringJSON, idNuevoRegistro);
}
} finally {
if (rs != null) {
rs.closeRecordStore();
}
}
}
public int addRecord(String stringJSON, RecordStore rs) throws JSONException,RecordStoreException {
byte[] raw = stringJSON.getBytes();
int idNuevoRegistro = rs.addRecord(raw, 0, raw.length);
return idNuevoRegistro;
}
public Customer initializeCustomer(String stringJSON, int idRecord) throws JSONException {
Customer c = new Customer();
JSONObject jsonObj = new JSONObject(stringJSON);
// Set Customer properties
//...
return c;
}
}
This class is used to show a list of customer and ,as you can see, it extends the List class and receives an array of Customers as a parameter.
public class ListCustomers extends List {
private final Customer[] mData;
public static ListCustomers create(Customer[] data) {
int i = 0;
for (; i < data.length; i++) {
if (data[i] == null) {
break;
}
}
String[] names = new String[i];
for (int j = 0; j < i; j++) {
names[j] = data[j].name;
}
return new ListCustomers(names, data);
}
protected ListCustomers(String names[], Customer[] data) {
super("List of Customer", IMPLICIT, names, null);
mData = data;
}
public Customer getSelectedObject() {
return mData[this.getSelectedIndex()];
}
}
Finally this is how I call the thread from the MIDlet (using all the 3 previous classes) when I want to show a List of Customers:
private void showCustomerList(String url) {
showWaitForm();
if (scrCustomerList == null) {
rmsCustomers = new RMSCustomer("rmsCustomers");
thrLoadCustomers load = new thrLoadCustomers(rmsCustomers, url);
Thread t = new Thread(load);
t.start();
try {
t.join();
} catch (InterruptedException ex) {
}
scrCustomerList = ListCustomers.create(rmsCustomers.getCustomers());
scrCustomerList.addCommand(cmdSelect);
scrCustomerList.addCommand(cmdBack);
scrCustomerList.setCommandListener(this);
}
mDisplay.setCurrent(scrCustomerList);
}
Now here's the problems I have :
The showWaitForm() doesn't work (it sets a form with a Gauge as the
Current form)
I don't know how to show all the exceptions that might be thrown from
within the thrLoadCustomers class.
I don't know whether using t.join() is the best choice
The last question is about something the book I'm reading says :
Threads, in particular, can be a scarce commodity. The MSA
specification requires that an application must be allowed to create
ten threads. Just because you can doesn’t mean you should. In general,
try to use the fewest resources possible so that your application will
run as smoothly as possible
This is the first time a use threads, and in my app I might have up to 10 threads (classes). However, I will only execute once thread at the time, will I be going against what the previous quotation says??
I hope I'm not asking too many questions. Thank you very much for your help.
P.D Much of the code I posted here wouldn't have been possible with the help of Gregor Ophey
Question #1 is about a different problem not related to threading, and for which very little code is shown. I'd suggest you to post a new dedicated question with proper explanation of the issue.
Questions #2 and #3: You could define a wrapper class like this:
public class WSResult {
private boolean success; //true if the WS call went ok, false otherwise
private String errorMessage; //Error message to display if the WS call failed.
private Object result; //Result, only if the WS call succeeded.
private boolean completed = false;
//TODO getter and setters methods here
}
In your screen, you can create an instance of result and wait for it:
WSResult result = new WSResult();
//Start thread here
new Thread(new LoadCustomersTask(result)).start();
//This is old school thread sync.
synchronized(result){
while(!result.isCompleted()){
result.wait();
}
}
//Here the thread has returned, and we can diaplay the error message if any
if(result.isSuccess()){
} else {
//Display result.getErrorMessage()
}
Then your runnable would be like this:
class LoadCustomersTask implements Runnable {
private final WSResult result;
public LoadCustomersTask(WSResult res){
result = res;
}
public void run(){
//Do the WS call
//If it went well
result.setSuccess(true);
result.setResult(jsonResultados);
//Else
result.setSuccess(false);
result.setErrorMessage("Your error message");
//In any case, mark as completed
result.setcompleted(true);
//And notify awaiting threads
synchronized(result){
result.notifyAll();
}
}
}
You can also do it with thread.join, but wait/notify is better because you not making the screen depend on the particular thread where the runnable runs. You can wait/notify on the result instance, as shown, or on the runnable if it is intended for a single use.
Question #4: Yes threads must not be abused, specially in JavaME where programs usually run in single core CPUs with a frecuency in the order of MHz. Try not to have more than 1-3 threads running at the same time. If you really need to, consider using a single thread for running all background tasks (a blocking queue).
I am reading Java Concurrency in Practice and got stuck on this program,where author say it won't cause the deadlock. But if I swap the method arguments then it will cause deadlock.
Obj1 = hash-code =1
Obj2 = hash-code =2
Thread1. transfer(Obj1, Obj2) will get lock on fromAcct and will wait for toAcct lock
Thread2.transfer(Obj2, Obj1) will get lock on toAcct and will wait for fromAcct lock
So basically we ended-up in dead-lock.
My question is how come is the dead-lock is avoided in following code.
public class InduceLockOrder {
private static final Object tieLock = new Object();
public void transferMoney(final Account fromAcct,
final Account toAcct,
final DollarAmount amount)
throws InsufficientFundsException {
class Helper {
public void transfer() throws InsufficientFundsException {
if (fromAcct.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAcct.debit(amount);
toAcct.credit(amount);
}
}
}
int fromHash = System.identityHashCode(fromAcct);
int toHash = System.identityHashCode(toAcct);
if (fromHash < toHash) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {
synchronized (toAcct) {
synchronized (fromAcct) {
new Helper().transfer();
}
}
} else {
synchronized (tieLock) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
}
}
}
interface DollarAmount extends Comparable<DollarAmount> {
}
interface Account {
void debit(DollarAmount d);
void credit(DollarAmount d);
DollarAmount getBalance();
int getAcctNo();
}
class InsufficientFundsException extends Exception {
}
}
Your example states that it is in fact correct.
Case 1: Thread1.transfer(Obj1, Obj2)
In this case, fromHash < toHash so the first lock is on the fromAcct param or Obj1 and the second is on the toAcct param or Obj2
Case 2: Thread1.transfer(Obj2, Obj1)
In this case, fromHash > toHash so the first lock is on the toAcct param or Obj1 and the second is on the fromAcct param or Obj2
Lock order is same, so no deadlock occurs.